Il linguaggio macchina è sostanzialmente una traduzione in binario del linguaggio assembly. Di conseguenza, come probabilmente già sai, l'assembly non è un linguaggio di programmazione vero e proprio, ma una famiglia di linguaggi di programmazione: l'assembly dei processori x86_64 (eg. il tuo computer) è diverso dall'assembly degli ARM (eg. il tuo smartphone), ma rimangono molto simili in quanto sono entrambi delle traduzioni da stringhe binarie (0 e 1) a caratteri ASCII (A-Z, 0-9 e qualche simbolo).
Nel momento in cui viene progettata una CPU (o meglio, un'architettura) si definisce l'
ISA (Instruction Set Architettcture), ovvero quali sono quali sono le istruzioni che il processore andrà ad eseguire (somma, moltiplicazione, ecc...) e come funzionano (dimensione dei registri, indirizzamento della memoria, ecc...). Nel momento in cui si progetta un'ISA ci sono principalmente due strade che si possono prendere: RISC (Reduced Instruction Set Computer) oppure CISC(Complex Instruction Set Computer). Le architetture RISC (eg. l'ARM che hai nello smartphone) si basano sul definire solo solo poche istruzioni (poche decine) e di effettuare le operazioni più complesse a livello software, con la composizione di queste poche e semplici istruzioni, mentre le architetture CISC (eg. l'x86_64 che hai nel pc) tendono a definire molte istruzioni (diverse centinaia) che generalmente fanno cose anche abbastanza complesse. La sostanza di questo discorso è che non è necessario avere tante istruzioni: con qualche decina di comandi assembly riesci a fare tutti i programmi che vuoi, esiste anche un caso limite (praticamente inutilizzato) dove usando
soltanto un'istruzione si riesce a fare tutto.
Ogni istruzione in linguaggio macchina è identificata da un codice operativo (OP Code), ovvero parte della stringa binaria che forma l'istruzione serve per "dare un nome" all'istruzione. Per esempio, se tutte le istruzioni hanno una lunghezza prefissata di 32 bit, chi progetta l'ISA può scegliere che i primi 4 bit servono per identificare l'istruzione: quindi potrebbe dire che le istruzioni che iniziano per 0000 sono le addizioni, quelle che iniziano per 0001 sono le sottrazioni, quelle che iniziano per 0010 sono le moltiplicazioni, ecc... Se un codice operativo è formato da n bit, avremo 2^n possibili op codes.
Gli altri bit che formano un'istruzione hanno un significato dipendente dall'operazione stessa: ad esempio, se voglio prelevare un valore dalla memoria centrale (RAM) gli altri bit potrebbero servirmi per scegliere l'indirizzo del dato in questione; mentre se voglio effettuare un'addizione tra registri (registroA = registroB + registroC), gli altri bit potrebbero servirmi per "dare un nome" ai registri.
Nel momento in cui un processore è in funzione, il suo lavoro può essere diviso principalmente in queste fasi: prelevare la prossima istruzione dalla memoria, capire di che istruzione si tratta (guardando l'op code) ed eseguire tale operazione. L'esecuzione di queste fasi viene chiamata
fetch-decode-execute cycle. Per eseguire queste fasi il processore usa, in grandissima quantità, un componente elettronico chiamato
transistor. A mo' di Lego, questi transistor vanno a formare le
porte logiche, dei dispositivi che permettono di effettuare le operazioni logiche (and, or, not, xor, ecc...) e che a loro volta si compongono per formare circuiti più complessi (
multiplexers,
flip-flop, ecc...); questi circuiti più complessi formano circuiti ancora più complessi (
shift registers,
ALU, ecc...) e così via fino ad avere un sistema (la CPU) in grado di effetturare il fetch-decode-execute cycle che ti ho brevemente descritto in precedenza.
Tra questi componenti, possiamo dire che il transistor sia l'unico che vede il segnale elettrico definito dalla differenza di potenziale (i volt). Dalle porte logiche in poi non si parla più di quanti volt ci sono su un certo connettore, ma si parla di stati. Gli input e gli output di una porta logica sono degli stati e questi stati possono essere vero (1) oppure falso (0). Avere lo stato vero piuttosto che lo stato falso è giustificato dalla differenza di potenziale (volt) tra i due connettori: se c'è un cambiamento sufficiente a far "scattare" il transistor, c'è un cambiamento di stato.