# Most bare-bones way to use a CPU? (How to learn)

I know a bit of programming but am brand new to lower-level stuff so please forgive the naivety of this question. What would be the most direct way of executing a series of commands on a CPU to do a basic addition of 1+2=3? For example, at a relatively high-level, I could buy a micro-controller such as an an Arduino and write C code and compile it/execute it on the device, for example:

int main() {
int a = 1;
int b = 2;
return a + b;
}


Would it be possible to purchase something like an Intel i7, and, execute the assembly (compiled binary) on the chip without a whole bunch of peripherals? I guess my question is, given a CPU, what is the bare minimum that would be needed in order to execute instructions on it and return/receive some sort of output? I know in "build-your-own-computers" there's the CPU, Motherboard, RAM, monitor. power-supply, etc. but what would be the most bare-bones way to do this?

The point of this is I'm learning assembly/instructions and I'd like to go as low-level as I can go to understand how the components work, without going into the actual electronics fabrication (other than settings things up/connecting them).

• The more complex the chip is, the less feasible it becomes to code it in bare metal if your goal is to actually do something high level. Remember, even if you type in assembly to perform a simple addition, you need to also write in assembly to set everything (oscillator, IO, memory, everything) up to actually get that result out of the processor so you can check it, and this can be a daunting task. And let's not forget an I7 requires a motherboard and its associated mandatory components to run. Jul 29, 2020 at 19:22
• Where would you store the code? How would you load it into memory? How would you point the i7 at the memory? Full-on processors are complicated... Jul 29, 2020 at 19:25
• I think you would actually learn a lot more, a lot more easily designing a processor for yourself in an FPGA and then writing assembly for that. What you propose above is either learning to write drivers or firmware for the I7 so low-level and highly optimized that you use assembly instead of a higher level language, or are asking if you can learn how to clear a beach by using tweezers instead of a shovel. Jul 29, 2020 at 19:27
• This question has serious problems of breadth as you fail to distinguish between hardware and software requirements, and you span both tiny flash-based MCU's and high end PC processors which need a whole board worth of support. Realistically, get an Arduino and after making sure it works learn to write bare metal code (keep using the bootlader, that is not cheating) or get an STM32 Discovery/Nucleo board. Jul 29, 2020 at 19:30
• Doesn't get more bare bones than a PDP-11 front panel. You can do the same with a Z80, 16 Address switches, 8 Data switches and a pushbutton for "Enter". But not with an i7, sorry. obsolescence.wixsite.com/obsolescence/pidp-11 Jul 29, 2020 at 19:35

You dont want to mess with an intel, no. Not a good starting path at all. Unless perhaps you run one of the 8086/88 simulators, but even then I would not start there.

As far as learning assembly goes, pdp11 (not joking), msp430, arm thumb, arm, and a few others are good starting points, build a good foundation then each subsequent instruction set is easier. You can start with simulations and that is not a bad path, there are pdp11 simulators (simh), msp430 there is an open core, armv2/3 open core plus countless mips and risc-v's I would do those later. You want something supported by gnu tools so all of the above (yes there is a maintainted pdp-11 backend on binutils and gcc). msp430 and countless arm based microcontrollers are available and those would be the best hardware place to start, that or a pi-zero (but not a full sized pi nor beagle bone black not yet). yes there are avr's, etc but can be harder to use depending on what one you get.

Realistically there are many mcu boards in the $10 to$20 range that would be a good starting point from a cost perspective, some may require another or few $2 -$20 boards. Going to pick one path first.

st is one of the leading companies with cortex-m (arm) based mcus that run in thumb mode which is one of the simpler arm instruction sets. The instruction set with the most compatibility across the whole of the arm based world.

Running on linux will make your life much much easier but not required, tools can be found for Windows or OSX.

A minimal program to add two numbers for a cortex-m would be:

.thumb
.word 0x20001000
.word reset
.thumb_func
reset:
mov r0,#1
mov r1,#2
b .


that is it, 100% of the software

arm-linux-gnueabi-as flash.s -o flash.o
arm-linux-gnueabi-ld -Ttext=0x08000000 flash.o -o flash.elf
arm-linux-gnueabi-objcopy -O binary flash.elf flash.bin
arm-linux-gnueabi-objdump -d flash.elf > flash.list


The code in this answer doesnt care between arm-none-eabi- and arm-linux-gnueabi- built gnu tools.

cat flash.list

Disassembly of section .text:

08000000 <_start>:
8000000:   20001000
8000004:   08000009

08000008 <reset>:
8000008:   2001        movs    r0, #1
800000a:   2102        movs    r1, #2
800000c:   180a        adds    r2, r1, r0
800000e:   e7fe        b.n 800000e <reset+0x6>


I am leading up to a particular board/chip/family the core actually looks for the vector table at 0x00000000 but this brand addresses the user flash at 0x08000000 then mirrors it to 0x00000000 if the chip is configured to boot from the user flash. The first word goes into the stack pointer and the second is the reset vector address orred with one (a legacy thumb thing, roll with it).

And then you can see one register is loaded with a 1, one with a 2 then they are added together and stored in a third.

I am starting with a NUCLEO-F446RE which is currently readily available from amazon, mouser, digikey, st, etc. Cheaper from places other than amazon but then you pay shipping. There are $10 nucleo boards that work just fine as well. Some of the nucleo boards dont hook the target mcu uart through the debug end of the board (another mcu), so you want to do your research first. If you get one of these boards, then you dont for now need to buy any other hardware this is it. The board when plugged in shows up as a virtual thumb drive, and you simply copy the .bin file over in order to load the program into the flash. cp flash.bin /media/user/NODE_F446RE/  Now the program is loaded the processor reset and the program is running. Using another free tool, openocd I can connect to the debugger in the mcu: openocd -f /usr/share/openocd/scripts/interface/stlink-v2-1.cfg -f /usr/share/openocd/scripts/target/stm32f4x.cfg Open On-Chip Debugger 0.10.0 Licensed under GNU GPL v2 For bug reports, read http://openocd.org/doc/doxygen/bugs.html Info : auto-selecting first available session transport "hla_swd". To override use 'transport select <transport>'. Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD adapter speed: 2000 kHz adapter_nsrst_delay: 100 none separate Info : Unable to match requested speed 2000 kHz, using 1800 kHz Info : Unable to match requested speed 2000 kHz, using 1800 kHz Info : clock speed 1800 kHz Info : STLINK v2 JTAG v36 API v2 SWIM v26 VID 0x0483 PID 0x374B Info : using stlink api v2 Info : Target voltage: 3.266535 Info : stm32f4x.cpu: hardware has 6 breakpoints, 4 watchpoints  in another window telnet into the openocd debugger, stop the core and dump the registers telnet localhost 4444 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. Open On-Chip Debugger > halt target halted due to debug-request, current mode: Thread xPSR: 0x01000000 pc: 0x0800000e msp: 0x20001000 > reg ===== arm v7m registers (0) r0 (/32): 0x00000001 (1) r1 (/32): 0x00000002 (2) r2 (/32): 0x00000003 (3) r3 (/32): 0x00000000 (4) r4 (/32): 0x00000000  Can see that the three registers have the values we programmed. As well as the vector table loaded stack pointer (13) sp (/32): 0x20001000  If I want to do this in C and make it slightly more complicated, Ill go ahead and make a more complete project. 100% of the code: flash.s .cpu cortex-m0 .thumb .thumb_func .global _start _start: .word 0x20001000 .word reset .thumb_func reset: bl main b .  main.c unsigned int fun ( unsigned int a, unsigned int b ); int main ( void ) { return(fun(1,2)); }  fun.c unsigned int fun ( unsigned int a, unsigned int b ) { return(a+b); }  flash.ld MEMORY { rom : ORIGIN = 0x08000000, LENGTH = 0x1000 ram : ORIGIN = 0x20000000, LENGTH = 0x1000 } SECTIONS { .text : { *(.text*) } > rom .rodata : { *(.rodata*) } > rom .bss : { *(.bss*) } > ram }  build arm-linux-gnueabi-gcc -Wall -O2 -ffreestanding -mcpu=cortex-m0 -mthumb -c main.c -o main.o arm-linux-gnueabi-gcc -Wall -O2 -ffreestanding -mcpu=cortex-m0 -mthumb -c fun.c -o fun.o arm-linux-gnueabi-ld -nostdlib -nostartfiles -T flash.ld flash.o main.o fun.o -o main.elf arm-linux-gnueabi-objdump -D main.elf > main.list arm-linux-gnueabi-objcopy -O binary main.elf main.bin  Your example program is dead code as written if optimized it would not generate an add instruction it would instead be: main: mov r0,#3 bx lr  there are other solutions but hiding the constants from the optimization 08000000 <_start>: 8000000: 20001000 andcs r1, r0, r0 8000004: 08000009 stmdaeq r0, {r0, r3} 08000008 <reset>: 8000008: f000 f802 bl 8000010 <main> 800000c: e7fe b.n 800000c <reset+0x4> ... 08000010 <main>: 8000010: b510 push {r4, lr} 8000012: 2102 movs r1, #2 8000014: 2001 movs r0, #1 8000016: f000 f801 bl 800001c <fun> 800001a: bd10 pop {r4, pc} 0800001c <fun>: 800001c: 1840 adds r0, r0, r1 800001e: 4770 bx lr  and the add happens. r0 gets the 1, r1 the 2, then the add turns r0 into the 3. So copy and run > reg ===== arm v7m registers (0) r0 (/32): 0x00000003 (1) r1 (/32): 0x00000002 (2) r2 (/32): 0x00000000  Last example adding this to flash.s .globl dummy .thumb_func dummy: bx lr  and using this main.c void dummy ( unsigned int ); int main ( void ) { unsigned int ra; for(ra=0;;ra++) dummy(ra); } Disassembly of section .text: 08000000 <_start>: 8000000: 20001000 8000004: 08000009 08000008 <reset>: 8000008: f000 f802 bl 8000010 <main> 800000c: e7fe b.n 800000c <reset+0x4> 0800000e <dummy>: 800000e: 4770 bx lr 08000010 <main>: 8000010: b510 push {r4, lr} 8000012: 2400 movs r4, #0 8000014: 0020 movs r0, r4 8000016: f7ff fffa bl 800000e <dummy> 800001a: 3401 adds r4, #1 800001c: e7fa b.n 8000014 <main+0x4> 800001e: 46c0 nop ; (mov r8, r8)  so both r4 and r0 will contain the count value, if we stop and start and stop we can see those count > halt target halted due to debug-request, current mode: Thread xPSR: 0x01000000 pc: 0x0800000e msp: 0x20000ff8 > reg r0 r0 (/32): 0x0088B275 > resume > halt target halted due to debug-request, current mode: Thread xPSR: 0x01000000 pc: 0x0800000e msp: 0x20000ff8 > reg r0 r0 (/32): 0x009B0F27 > reg r4 r4 (/32): 0x009B0F27 >  You can then move on to blinking leds, getting the uart initialized and so on. Here is where you understand that baremetal programming is 99% reading and research, less than 1% of your time is spent on the actual application. You do lots of reading and write some amount of throwaway code figuring out what the manual really meant. You also find that most of the work has to do with the peripherals and not the architecture/instruction set. You spend more time dealing with configuring peripheral registers than dealing with configuring the processor if at all. So there are lots of nucleo boards that support this "mbed" thing which implies that you can do the copy the bin file thing. There are nxp based ones as well as the first mbeds were nxp based. (nxp is another company that sells cortex-m based chips). If you invest in a nucleo board like the one mentioned above the debug end of the board can be used to debug other cortex-m based mcus even from other vendors, or for a few bucks you can get an swd based debugger or generic ftdi breakout that supports mpsse (adafruit has one for$15). If you are willing to gamble on ebay you can get a usb uart board for a couple of bucks and an swd debugger for five bucks. Or spend around $15 for each through amazon, adafruit or sparkfun, things that you will want in your toolbox. Another path that is also arm based is the raspberry pi zero and a simple program on that is: mov r0,#1 mov r1,#2 add r2,r1,r0 b .  no vector table. Disassembly of section .text: 00000000 <.text>: 0: e3a00001 mov r0, #1 4: e3a01002 mov r1, #2 8: e0812000 add r2, r1, r0 c: eafffffe b c <.text+0xc>  you then copy the .bin file to an sd card and name it kernel.img. along with bootcode.bin and start.elf that you get from the raspberry pi folks github projects. insert the sd card into the board and power it on. you can do jtag debugging using openocd but you will need hardware and you cant debug the above program. The chip is gpu centric the exposed jtag is the gpu, to connect to the arm jtag debugger you need to re-configure some gpio pins to expose the jtag which means some extra arm code that configures those gpio pins, then the right ftdi breakout or other board and four jumper wires for the basic jtag signals and you can use openocd to connect to the processor and stop it and such. Since it runs from ram, once stopped then you could load a program load_image main.elf resume 0x00000000  then halt load resume, repeat until you hang some peripheral or figure your program out, occasionally needing to reset to put the chip in a known state. The other pis are much more complicated, you dont want to go there with baremetal for a long time. The good thing about the pi's though is an excellent baremetal forum at the site with lots of folks with deep understanding of the arm core and the chips plus example code of how to do various things including the magic you will need if you want to dabble into the multi-core armv7 or armv8 cores on that platform. There is a risc-v based board for$9 on amazon, that has a similar fake flash based loading scheme risc-v is a bit harder to get started on than arm/thumb. so I wouldnt start here, and I dont remember what debug if anything is available on this board, so you somewhat blindly aim for blinking the led then later uart, then you can start using those to debug.

You can certainly buy arduino boards and ditch the arduino sandbox and write programs and load them, and do similar things as above. There is an avr instruction set simulator you can use. The harvard-ish nature of the instruction set and some other things I wouldnt start there, i migth go there later after some other instruciton set.

msp430 is a good simple starting point clearly inspired by the pdp11/lsi11 which is one of the better first instruction sets. Boards used to be a few bucks but now are probably more like $10 to$20 and can at least program the chip and blink leds for just the cost of the board with the right software. If you want to do uart, etc you might need a usb uart board for a few to 15 bucks.

opencores has some cores you can sim with tools like verilator or icarus, but you may end up spending a lot of time getting that all working esp if you have no experience with these tools or these hardware languages. But take the amber arm2/3 core or one of the others, figure out how to feed the core, they you can get a full experience of seeing the code execute in a core, watch the instructions get fetched, decoded, etc, etc...Once you develop code for a chip in a sim it is difficult when the silicon arrives because you now have no visibility into what is going on. (granted the silicon runs orders of magnitude faster).

Another approach is to write your own instruction set simulator. msp430 you can probably bang out in a long afternoon. minimal thumb maybe a full day. then learn to master the toolchain (just use gnu binutils and gcc) to create binaries as above, then feed your sim these programs and make/watch them run. Writing a simulator and/or disassembler you will learn the instruction set nuances better than some professionals that have been using that instruction set for years since so few really dig into the nuances. Using a debugged toolchain ideally means the code is good and things that are not always well documented like pc-relative addressing or branching is generated in the machine code and you can figure out how to decode that.

End of the day, dont limit yourself to one platform, the approach above has suggestions at approaches that will hopefully maximize your initial success, limit your frustration and desire to give up and never try again. With some marks in the win column that drives motivation to keep on and grind through the failures to get at more wins. x86 is a bit of a beast, you can certainly work your way up to it, but why? You are not going to boot one from scratch, you can certainly start from where something like windows or linux boots from media and go from there. An x86 box has more non-x86 processors than x86 processors, there are more arm processors in that box than x86s, and/or a smattering of others, 8051s, z80s, etc. x86 is rapidly becoming limited to servers and for now ARM's for everything else (phones and tablets and soon to be laptops). It is interesting to see what a full blown CISC looks like and how it works, and personally I would start with an 8088/86 emulator and have a manageable and safe experience with a high chance of success. And then read sites like stackoverflow and intel docs to see how the current instruction set evolved over time as well as the gory details of all the protection systems, etc.

I highly recommend you instead start this journey with microcontrollers and/or simulators/emulators. Inexpensive, folks still develop at this level, if one doesnt work for you or you brick it oh well buy another or buy a different one for another 10 bucks. Depending on the journey you want to take you can then use free tools like kicad, and buy some \$1 mcu parts, a few dollar pcb, solder the chip on yourself and open up even more chips to play with that dont have cheap eval boards.

EDIT

rereading the quesiton, "seeing" output other than the debugger visibility above, for that nucleo board

flash.s

.cpu cortex-m0
.thumb

.thumb_func
.global _start
_start:
.word 0x20001000
.word reset

.thumb_func
reset:
bl notmain
b hang
.thumb_func
hang:   b .

.thumb_func
.globl dummy
dummy:
bx lr


notmain.c

void dummy ( unsigned int );

#define RCCBASE         0x40023800
#define RCC_AHB1ENR (*((volatile unsigned int *) (RCCBASE+0x30) ))

#define GPIOABASE       0x40020000
#define GPIOA_MODER (*((volatile unsigned int *) (GPIOABASE+0x00) ))
#define GPIOA_BSRR  (*((volatile unsigned int *) (GPIOABASE+0x18) ))

//PA5

int notmain ( void )
{
unsigned int ra;
unsigned int rx;

ra=RCC_AHB1ENR;
ra|=1<<0; //enable GPIOA
RCC_AHB1ENR=ra;

ra=GPIOA_MODER;
ra&=~(3<<(5<<1)); //PA5
ra|= (1<<(5<<1)); //PA5
GPIOA_MODER=ra;

for(rx=0;;rx++)
{
GPIOA_BSRR=((1<<5)<< 0);
for(ra=0;ra<800000;ra++) dummy(ra);
GPIOA_BSRR=((1<<5)<<16);
for(ra=0;ra<800000;ra++) dummy(ra);
}
return(0);
}


flash.ld

MEMORY
{
rom : ORIGIN = 0x08000000, LENGTH = 0x1000
ram : ORIGIN = 0x20000000, LENGTH = 0x1000
}
SECTIONS
{
.text   : { *(.text*)   } > rom
.rodata : { *(.rodata*) } > rom
.bss    : { *(.bss*)    } > ram
}


build

arm-linux-gnueabi-as --warn --fatal-warnings -mcpu=cortex-m0 flash.s -o flash.o
arm-linux-gnueabi-gcc -Wall -O2 -ffreestanding -mcpu=cortex-m0 -mthumb -c notmain.c -o notmain.o
arm-linux-gnueabi-ld -nostdlib -nostartfiles -T flash.ld flash.o notmain.o -o notmain.elf
arm-linux-gnueabi-objdump -D notmain.elf > notmain.list
arm-linux-gnueabi-objcopy -O binary notmain.elf notmain.bin


copy it over and the led will blink.

Disassembly of section .text:

08000000 <_start>:
8000000:   20001000
8000004:   08000009

08000008 <reset>:
8000008:   f000 f804   bl  8000014 <notmain>
800000c:   e7ff        b.n 800000e <hang>

0800000e <hang>:
800000e:   e7fe        b.n 800000e <hang>

08000010 <dummy>:
8000010:   4770        bx  lr
...

08000014 <notmain>:
8000014:   2101        movs    r1, #1
8000016:   b5f0        push    {r4, r5, r6, r7, lr}
8000018:   46c6        mov lr, r8
800001a:   4a12        ldr r2, [pc, #72]   ; (8000064 <notmain+0x50>)
800001c:   b500        push    {lr}
800001e:   6813        ldr r3, [r2, #0]
8000020:   2780        movs    r7, #128    ; 0x80
8000022:   430b        orrs    r3, r1
8000024:   4910        ldr r1, [pc, #64]   ; (8000068 <notmain+0x54>)
8000026:   6013        str r3, [r2, #0]
8000028:   680b        ldr r3, [r1, #0]
800002a:   4a10        ldr r2, [pc, #64]   ; (800006c <notmain+0x58>)
800002c:   4e10        ldr r6, [pc, #64]   ; (8000070 <notmain+0x5c>)
800002e:   401a        ands    r2, r3
8000030:   2380        movs    r3, #128    ; 0x80
8000032:   00db        lsls    r3, r3, #3
8000034:   4313        orrs    r3, r2
8000036:   600b        str r3, [r1, #0]
8000038:   2320        movs    r3, #32
800003a:   4698        mov r8, r3
800003c:   4d0d        ldr r5, [pc, #52]   ; (8000074 <notmain+0x60>)
800003e:   03bf        lsls    r7, r7, #14
8000040:   4643        mov r3, r8
8000042:   2400        movs    r4, #0
8000044:   6033        str r3, [r6, #0]
8000046:   0020        movs    r0, r4
800004a:   f7ff ffe1   bl  8000010 <dummy>
800004e:   42ac        cmp r4, r5
8000050:   d1f9        bne.n   8000046 <notmain+0x32>
8000052:   2400        movs    r4, #0
8000054:   6037        str r7, [r6, #0]
8000056:   0020        movs    r0, r4
800005a:   f7ff ffd9   bl  8000010 <dummy>
800005e:   42ac        cmp r4, r5
8000060:   d1f9        bne.n   8000056 <notmain+0x42>
8000062:   e7ed        b.n 8000040 <notmain+0x2c>
8000064:   40023830
8000068:   40020000
800006c:   fffff3ff
8000070:   40020018
8000074:   000c3500


Sounds like you want to work with a microcontroller. You can write assembly language code easily for an 8-bit microcontroller. You could use an Arduino board and AVR Studio and program it through the ICSP header with an appropriate tool. Or a PIC etc.

It's feasible to program ARM and even more complex chips in assembler but there is a lot of tedious setup and it would not be pleasant (often we use tools just to crank out the configuration code with more complex chips).

With a simple 8-bit processor there might only be 5 or 10 arcane instructions and some configuration variables set up to get it to chooch.

• You hardly need to use ICSP to put bare metal code on an Arduino board; the bootloader and avrdude don't care where the hex file came from, and once the chip is programmed they don't have any role in operating it except for that brief moment before the bootloader hands over execution, which is really just a time delay away from booting directly. Jul 29, 2020 at 19:32
• @ChrisStratton True. Depends how bare to the metal you want to go. Jul 29, 2020 at 19:34
• Not really, code flashed by a bootloader is no less "bare to the metal" than code that got in there by any other means Jul 29, 2020 at 19:36
• But how does that matter? If it did some actual setup it didn't revert, it would. But it doesn't - you can load the exact same hex file with or without a bootloader, just as long as it isn't so large that it extends to the bootloader area. I mean it's not like ISCP is "cheating" unless you do it with a toggle switch, LED... and is having a debounced pushbutton allowed? Using the bootloader doesn't change the task, it just limits the investment to a single bargain clone board. Jul 29, 2020 at 19:54
• @ChrisStratton I don't think it's cheating, but it's not identical to starting from bare metal. The registers are probably not exactly in the power-up default states and the startup timing is quite different. We see questions regularly here from folks who have gotten stuck from differences between starting with a debugger and starting from power-up. I hope this doesn't come across as nitpicking, it's caused me some hair loss from time to time. Jul 29, 2020 at 19:57

This is a pretty good resource if your learning the x86 instruction set: Intel Microprocessors

I used this book to build my own 8088 microprocessor computer, I started with the processor and ROM and then worked my way up to adding RAM and peripherals (like I/O). Very educational.

Because x86 instruction sets built upon the previous set, the instructions are mostly the same (with additions to instructions and bus width)

If you don't want to build a computer, maybe an 8051 microcontroller would be better.

• When you say build, do you mean implement? Or build-build? Like the logic of 8088 from scratch? Jul 29, 2020 at 19:45
• I mean build a computer, you wire up the IC's Jul 29, 2020 at 20:36
• @VoltageSpike thanks for this answer. Could you please explain a bit more about that book and how that could help/guide me in building up a processor or running code on it? Jul 29, 2020 at 20:50
• It explains everything about the intel instruction set and how processors work, it explains how instructions are booted, how they are loaded into the processor, how memory is accessed and how peripherals are accessed. Jul 29, 2020 at 20:52
• I have a version of that book. Its pretty good. Jul 30, 2020 at 4:38

MARS MIPS simulator

You can use this simulator to write assembly code that you can then compile and run.

I took an advanced embedded systems class and we used this simulator to write simple programs for a MIPS processor. If I remember correctly we used this book to learn about the architecture:

MIPS book

The benefit of using this book and simulator is that you will get a good fundamental understanding of what is happening in a CPU.

If you really want to learn a lot, you can then use a hardware description language to design your own MIPS processor. There are complete college courses on YouTube that do just that. Then you could simulate your design and see the inner workings of the CPU. You could even go so far as to load your HDL into an FPGA and attach peripherals like buttons and LEDs and make a working computer.

Would it be possible to purchase something like an Intel i7, and, execute the assembly (compiled binary) on the chip without a whole bunch of peripherals?

No! But you could use a simpler CPU which requires very little support circuitry.

I know in "build-your-own-computers" there's the CPU, Motherboard, RAM, monitor. power-supply, etc. but what would be the most bare-bones way to do this?

At a minimum you need the CPU itself, a clock source, some 'memory' to hold the program code, and an output to see what it did. The most bare-bones possible circuit consists of the CPU, a crystal or external oscillator, jumpers or switches on the data bus to generate an opcode, and perhaps LEDs on some signal lines to show what the CPU is doing. Here's an example:-

absolute minimum Z80 CPU test circuit

But this can only execute one instruction over and over, good for for testing basic CPU operation but nothing else.

To run more complex code you need a parallel RAM or ROM chip, which you can program separately and then insert into the circuit. Next step is to add an address decoder so you can have both ROM and RAM, a machine code 'monitor' program in the ROM for interactive programming, and a serial I/O chip to connect to a 'terminal' (your PC), and then you have a complete single board computer.

• I love this offering. It's simple -- most especially because the Z80 was made to be simple for "regular folks" to wire up and use (night and day when compared to the NMOS 8080A with its 8238 which you pretty much had to have as doing it discretely was a nightmare.) The Altair 8800 front panel had all the needed logic to inject a single-stepped JMP so as to move the PC where you directed it. I suppose something similar could be added to the Z80 to make this more "real" in my mind.
– jonk
Jul 30, 2020 at 0:19

If you want to know everything about assembly language for the Intel processors I would check out Randall Hyde's "The Art of Assembly Language Programming" book. Its downloadable as a free PDF on his website.

https://www.plantation-productions.com/Webster/

He goes through everything from basic logic optimization to the details of how to program for 16-bit and 32-bit Intel processors as well as programming in assembly in a Linux or Windows environment.

Back when "CPU" meant a box with switches and light on the front panel, Then the minimum configuration would be the CPU itself, and a memory system, and the bus that connected them, and the power supplies.*

Then, you could use those switches and lights to examine and modify memory locations and the PC register, and at least one of those switches would let you "run" or "halt" or "single step" the program.

If you don't have a front panel, then you are going to need some kind of peripherals attached to the system to get information in and out.

A modern microcontroller is a single chip that has a CPU, RAM, programmable ROM, and various different peripherals all on the same chip. Microcontrollers need very little external support. A crystal, a couple of capacitors, 3.3V power, and, of course, some means to program it.

One way to get started with microcontrollers is to buy an eval board (e.g., https://www.digikey.com/product-detail/en/nxp-usa-inc/FRDM-KL25Z/FRDM-KL25Z-ND/3529594 ).

The one above has a microcontroller with some of the pins wired to useful things (a push button, an LED, a USB-to-Serial-port adapter,) and other pins connected to a header. It also (and this is the good part) has an on-board programmer, so you don't need to buy one of these. Power comes from your computer or from a USB wall wart via. the same USB connector that you use to program it.

It also should come with a link to all of the manuals and free-downloads for all of the programming tools that you'll need.

* Toward the end of the front-panel era, all that stuff became small enough to actually fit in the same box with the CPU itself!

If you have an old MP3 player supported by Rockbox, you might find looking through the source code used to boot the player very interesting. For example, on a Sandisk Clip, you can see the bootloader which is branched to immediately after the device finishes running its onboard mask ROM:

The main function sequentially calls:

system_init();
kernel_init();
enable_irq();
lcd_init();
backlight_init();
button_init_device();
storage_init()
usb_int()


From there you can see system configures its CPU, memory and peripheral hardware clocks, and then eventually works up to being a functioning MP3 player. Aside from a little bit of assembly that runs before the main() to set up the c runtime:

https://git.rockbox.org/cgit/rockbox.git/tree/firmware/target/arm/crt0.S

It is almost all in c. There are similar bootloaders for ipods, and many other devices.

In college, i programmed a Motorola 68000 firmware board in assembly. I had to actually look up the opcodes and type them in. It looks like similar boards are still available. http://www.easy68k.com/paulrsm/mecb/mecb.htm

How about a fun version that's ALMOST bare metal?

The actual silicon for the old 6502 chip (the cpu inside the original Apple-II,) has been completely taken apart by hobbyists, and you can play with it online right now. Type in some opcodes. Single-step through it, and watch the voltages changing on the aluminum traces inside the chip. Zoom in to examine interesting spots.

http://www.visual6502.org/

Here's their presentation on the project, w/Siggraph-2010 slide show:

.

Wow, also someone actually built one of these chips in hardware. On pcb using transistors! Plus some (non-canon) extra components, colored LEDs.

https://monster6502.com

.

PS

The Visual-6502 project is working on many other ancient primitive CPU chips, and it appears that they have the Moto 6800 up and running too...

http://www.visual6502.org/JSSim/expert-6800.html