¡40 líneas de código Python, escribe una CPU!

Tabla de contenido

I. Introducción

En segundo lugar, la composición de la CPU.

3. Principio de funcionamiento

4. Análisis detallado del trabajo de instrucciones de la CPU

5. Python se da cuenta de los diversos componentes de la CPU

6. CPU integrada

7. Programación para CPU, experimentando el flujo de trabajo de los antiguos programadores

8. Resumen


I. Introducción

¿Cómo funcionan las CPU? Es un problema de niebla que afecta a los usuarios novatos. Es posible que conozcamos algunas palabras, como contador de programa, RAM, registros, pero no tenemos una comprensión clara y general de cómo funcionan estas partes y cómo funciona todo el sistema en conjunto.

Este artículo utiliza cuarenta líneas de código Python para implementar una CPU mínima. Hágalo programable, admita operaciones de suma y resta, lectura y escritura de memoria, salto incondicional y funciones de salto condicional. La razón por la que se implementa una CPU relativamente simple es para que todos entiendan el principio de funcionamiento de la CPU en su conjunto y no se vean obstaculizados por los detalles prematuramente.

"

La CPU real consiste en producir triodos o tubos MOS mediante grabado en obleas de silicio.Estos triodos actúan como interruptores: la suma, la resta y el almacenamiento se realizan entre la apertura y el cierre del interruptor. Antes, usaba el código de Python para simular una CPU similar a este artículo, a partir de un interruptor. Pero aquí, simulamos la CPU a un nivel superior: use código para simular componentes grandes, para que todos puedan entender el funcionamiento de la CPU en principio.

"

En segundo lugar, la composición de la CPU.

Toda la CPU consta de componentes grandes, como se muestra en la siguiente figura:

CPU de arquitectura de Harvard

Esta CPU adopta la arquitectura Harvard (estructura Harvard). La estructura de Harvard es relativamente simple y fácil de entender, y es relativamente fácil de implementar. En la figura anterior, hay RAM de instrucciones y RAM de datos, y las dos RAM son símbolos importantes de la estructura de Harvard.

"

Dos arquitecturas de CPU comunes son las arquitecturas Harvard y Von Neumann. La arquitectura Harvard es un diseño de CPU que almacena instrucciones y datos del programa en el mismo bloque de RAM. La arquitectura de von Neumann (estructura de von Neumann), también conocida como estructura de Princeton, es un diseño de CPU que combina memoria de instrucciones de programa y memoria de datos.

"

La principal diferencia entre la estructura de Harvard y la estructura de von Neumann: el primer programa y los datos se almacenan en dos RAM, y el último se almacena en una RAM.

3. Principio de funcionamiento

Veamos cómo funciona cada parte individualmente y luego cómo funcionan juntas.

3.1 Principio de funcionamiento de cada componente

Cada componente de la figura anterior tiene un circuito físico correspondiente en la CPU real, y sus funciones son:

  • El contador de PC genera 0, 1, 2, ... contando desde 0. Se puede borrar, o se puede ingresar un número desde el exterior, y el conteo comienza desde este número. Esto se llama configuración. Se utiliza para indicar ubicaciones de acceso a programas y datos.

  • La RAM, una memoria de acceso aleatorio para almacenar datos, admite la lectura de datos según la dirección (0x01 tal forma) y la escritura de datos según la dirección y la señal de escritura w. Se utiliza para almacenar programas y datos.

  • Registro, la memoria que almacena información de 8 bits, escribe los datos actuales de acuerdo con la señal w es 1, y w es 0 significa lectura. Similar a la RAM, pero solo puede almacenar 8 bits de información. A menudo se usa para almacenar instrucciones, direcciones y calcular cantidades intermedias.

  • El sumador completa la suma y resta de dos números. Cuando sub es 1, significa resta. Cuando ci es 1, significa llevar. Este dispositivo es el dispositivo central utilizado para formar la ALU (Unidad Lógica Aritmética). La CPU real está construida con puertas lógicas, multiplicadores, unidades de operación lógica, etc.

  • El selector 21 es equivalente a un interruptor unipolar de doble tiro.De acuerdo con la señal s21, se determina si la salida de 8 bits proviene de la entrada de 8 bits izquierda o derecha.

3.2 Principio de trabajo colaborativo

Las flechas en la figura anterior indican la dirección del flujo de datos, también conocida como diagrama de ruta de datos. A partir del diagrama de ruta de datos, podemos analizar cómo está diseñada la CPU.

Toda la ruta de datos comienza desde el contador de programa pc, y el contador genera los números 0, 1, 2, 3, 4... desde 0. El código de programa y los datos se almacenan en la RAM de instrucciones y en la RAM de datos, respectivamente. RAM accede y almacena datos en ubicaciones representadas numéricamente. De acuerdo con la dirección del contador 0, 1, 2, etc., coloque los datos en la RAM en el registro de instrucciones IR y el registro de datos DR respectivamente. Los registros son equivalentes a contenedores y variables, que almacenan los datos que le proporciona la memoria RAM.

La decodificación del código de instrucciones en el registro de instrucciones genera instrucciones de control de la CPU, estos 0 y 1 representan señales de bajo y alto nivel respectivamente, y la señal de nivel controla si el sumador lleva o no, si permite la resta, si permite la escritura del registro , Seleccione qué entrada del selector 21 se emite, si se reinicia el contador, etc. Por lo tanto, las instrucciones son en realidad señales eléctricas que controlan la coordinación de varios componentes de la CPU.

Los datos en los registros de datos van respectivamente al sumador sumador para realizar operaciones de suma y resta y luego fluyen al selector 21, o pueden fluir directamente al selector 21 para esperar la selección. Después de que el selector 21 seleccione, los datos ingresan al registro de acumulación AC. Los datos del acumulador deciden si escribir o no según si la señal de CA es de nivel alto 1. Los datos del acumulador de CA participarán en el siguiente cálculo o se almacenarán en la RAM de datos de acuerdo con la señal w.

En este punto, hemos completado un cálculo, el contador del programa se incrementa en 1 y se realiza el siguiente cálculo. Si esta instrucción es una instrucción de salto, la dirección de destino del salto se asigna directamente al contador del programa y el programa comienza a ejecutarse desde la nueva dirección.

A continuación utilizamos un ejemplo práctico para ilustrar el proceso de ejecución de la CPU.

4. Análisis detallado del trabajo de instrucciones de la CPU

Un programa completo se compone de instrucciones y datos. Las instrucciones son responsables de controlar el trabajo cooperativo de varios componentes de la CPU, mientras que los datos participan en cálculos específicos. El programa de muestra completa 10+2-3 y luego realiza un ciclo para restar 3 hasta que el resultado es 0, el programa está completo. El registro de instrucciones es ramc y el registro de datos es ramd.

ramc = [0x18, 0x19, 0x1d, 0x02, 0x31, 0x30, 0x00]
ramd = [10, 2, 3, 0xff, 0x06, 0x02]

Aprendamos cómo las instrucciones controlan la CPU.

4.1 Instrucciones

Sabemos arriba que el contador del programa comienza a generar desde 0. Después de que la CPU complete la operación de cálculo, todo el contador se incrementará en 1 para obtener la siguiente instrucción para continuar con la ejecución. Tomemos la primera instrucción 0x18 obtenida cuando pc es cero como ejemplo para explicar cómo se decodifica la instrucción y luego controlar cada dispositivo de la CPU.

0x18 es 0b0001 1000 en binario. Cada bit binario apunta al terminal habilitado de un dispositivo. El llamado extremo habilitador es el interruptor que hace que esta parte funcione. Por ejemplo, los dos 1 aquí representan un nivel alto, que están conectados respectivamente al terminal de habilitación w del registro de datos DR y al registro de acumulación AC. De esta forma, cuando leemos 0x18, sabemos que la CPU escribirá los datos en la ruta de datos actual en el registro de datos y el registro de acumulación.

Específicamente, la relación correspondiente entre cada bit binario y las terminales de habilitación de cada dispositivo de la CPU es una instrucción. La conexión de diseño de este artículo es la siguiente. El estado actual es el estado del comando 0x18 que controla el dispositivo de la CPU.

Instrucción 0x18

Por analogía, cuando se requiera alguna función, configure la posición del dispositivo correspondiente para habilitar la conexión de la señal en un nivel alto, es decir, configúrelo en 1, y el conjunto de instrucciones se puede obtener de la siguiente manera.

conjunto de instrucciones de la CPU

Analicemos el proceso de ejecución específico con el ejemplo anterior.

4.2 Análisis del proceso de ejecución de instrucciones

Vamos a encenderlo y la pc empieza a funcionar desde cero.

Instrucción 0x18

  • En la figura anterior, cuando el contador del programa es 0, se accede al espacio 0 de la RAM de instrucciones y la RAM de datos, y 0x18 y 10 se almacenan en el registro de instrucciones y el registro de datos, respectivamente.

    • Instrucción 0x18, binario 0b0001 1000, esta es la instrucción de carga, que indica que el terminal de habilitación w del registro DR y el registro AC es 1 respectivamente.

    • Los datos 10 se almacenan como datos en los registros DR y AC

La operación de carga 10 se completa arriba.

Instrucción 0x19

  • Cuando pc es 1, accede al primer espacio de RAM de instrucciones y RAM de datos, 0x19 y 2 se almacenan en el registro de instrucciones y el registro de datos respectivamente.

    • Instrucción 0x19, binario 0b0001 1001, esta es la instrucción Agregar suma, que indica respectivamente: el registro DR guarda datos; se selecciona el selector 21 y se emite el resultado del cálculo del sumador; el resultado se guarda en AC.

    • Los datos 2 se almacenan en DR como datos, se agregan al contenido de AC 10 en el paso anterior y luego se almacenan en AC.

La operación de 10+2 se completa arriba.

Instrucción 0x1d

  • Cuando pc es 2, acceda al segundo espacio de RAM de instrucciones y RAM de datos, 0x1d y 3 se almacenan en el registro de instrucciones y el registro de datos respectivamente.

    • Instrucción 0x1d, binario 0b0001 1101, esta es la instrucción Substracción, indicando respectivamente: el registro DR guarda datos; comienza la resta soportada por el sumador; se selecciona el selector 21; el resultado de la operación se guarda en AC.

    • Los datos 3 se almacenan en AC como la diferencia entre los datos almacenados en DR y el resultado 12 del paso anterior AC.

La operación de 12-3 se completa arriba.

Instrucción 0x02

  • Cuando pc es 3, acceda al tercer espacio de RAM de instrucciones y RAM de datos, y almacene 0x02 en el registro de instrucciones.

    • Instrucción 0x02, binario 0b0000 0010, esta es la instrucción de almacenamiento de almacenamiento, en este momento, la señal w es 1, lo que indica abrir la señal de habilitación de la RAM de datos, por lo que 9 en el registro de CA se almacena en la posición 3 de los datos RAM.

    • Datos 0xff, porque dr es 0, por lo que el registro de datos no almacena datos.

Lo anterior completa la escritura 9 en la ubicación de RAM de datos 3.

Instrucción 0x31

  • Cuando pc es 4, acceda al cuarto espacio de RAM de instrucciones y RAM de datos, y almacene 0x31 en el registro de instrucciones.

    • Si pre es 1 y AC es 0, escriba 0x06 en la calculadora de pc, y el programa salta para ejecutarse cuando pc es 6, que es el último paso para ordenar a HLT que se detenga.

    • Si AC no es cero o pre no es 1, continúa ejecutando pc+1 hacia abajo, es decir, pc es 5.

    • Instrucción 0x31, binario 0b0011 0001, esta es la instrucción de salto a cero Jz, que indica restablecer el contador de PC de acuerdo con si el resultado de CA es cero y la señal de configuración del contador de programa pre es 1.

    • Datos, 0x06 se almacena en DR como datos. Restablezca el contador de la PC en función de que la señal previa sea 1 y la CA sea 0 no.

Lo anterior completa la función de saltar a diferentes posiciones según el resultado del cálculo sea cero o no.

Instrucción 0x30

  • Cuando pc es 5, acceda al quinto espacio de RAM de instrucciones y RAM de datos, y almacene 0x30 en el registro de instrucciones.

    • La instrucción 0x30, binario 0b0011 0000, es una instrucción de salto incondicional Jmp, que indica restablecer el contador de PC.

    • Datos, 0x02 se almacena en DR como datos, y el contador de PC se reinicia según la señal previa.

    • La instrucción a la que apunta 0x02 es 0x1d, que es una instrucción de resta, es decir, continúa realizando la operación -3.

    • En este momento, pc apunta a 0x02 y se vuelve a realizar el cálculo de resta.

La operación de continuar-3 se completa arriba.

Comando 0x00

  • Cuando pc es 6, acceda al sexto espacio de RAM de instrucciones y RAM de datos, y almacene 0x00 en el registro de instrucciones.

    • Comando 0x00, binario 0b0000 0000, este es el comando Hlt, que indica detener.

    • Los datos del comando no tienen sentido.

    • La dirección de esta instrucción solo puede saltar cuando pc es 4.

De hecho, después de comprender el proceso de ejecución y los ejemplos anteriores, básicamente puede comprender el principio de funcionamiento básico de la CPU. Implementemos estos dispositivos con lenguaje Python.

5. Python se da cuenta de los diversos componentes de la CPU

5.1 memoria RAM

Usamos list para almacenar datos. Este es un diseño muy simple y directo.

ramc = [0x18, 0x19, 0x1d, 0x02, 0x31, 0x30, 0x00]

La lectura y escritura de la memoria, según el puntero del pc, ramc[pc]=data significa escribir en la memoria, es decir, leer  ramc[pc].

5.2 sumador sumador

def adder(a=0, b=0, ci=0, sub=0):
    return a-b+ci if sub == 1 else a+b+ci

El sumador real usa puertas lógicas, que es equivalente a un montón de interruptores apilados juntos en una cierta relación Aquí usamos simulación de lenguaje de alto nivel, lo que simplifica enormemente la implementación. Este sumador realiza la suma de a y b, mientras que ci significa acarreo y sub significa resta.

5.3 Registro registro

El registro está diseñado utilizando el concepto de cierre de Python, que consiste en utilizar variables libres para recordar el último estado del registro. Cuando usamos  AC = register() call, AC es equivalente a la función interna devuelta register_inner, en este momento temp como una variable libre y register_inner pertenecen al mismo cierre. Por lo tanto, la lectura y escritura de la variable temporal es una variable persistente. Es equivalente a mantener el estado.

Escriba cuando la señal w sea 1, que es equivalente a la terminal de habilitación w del registro.

def register():
    temp = 0

    def register_inner(data=0, w=0):
        nonlocal temp
        if w == 1:
            temp = data
        return temp
    return register_inner
"

En el diseño de CPU real, cómo diseñar registros es una gran pregunta. Incluso en el aprendizaje superficial del modelo de CPU del curso de principio de microcomputadora, se necesita mucha reflexión para comprender que los relés y los triodos pueden memorizar. Este artículo utiliza un lenguaje de alto nivel para simular el hardware subyacente, solo podemos dar una vuelta, por lo que aquí necesitamos una comprensión profunda del concepto de cierres.

"

Selector de 5,4 8 bits 21

21 El selector devuelve b cuando sel end es 1. Cuando sel es cero, devuelve a. Es decir, una de las dos entradas se selecciona como salida.

def b8_21selector(a=0, b=0, sel=0):
    return a if sel == 0 else b

6. CPU integrada

Cuando integramos los diversos componentes de la CPU, primero creamos nuevos componentes, luego los inicializamos y finalmente configuramos la PC a cero para iniciar un ciclo infinito.

En el proceso del ciclo, primero escriba los datos en la RAM de instrucciones del programa en el registro de instrucciones, decodifique cada señal de control de acuerdo con el registro de instrucciones y luego opere bajo el control de la señal de control de instrucciones.

Primero, si el registro de comando IR es el comando de parada HLt, entonces el sistema se rompe. De lo contrario, decida si desea escribir la señal de datos en el registro de datos DR de acuerdo con dr.

El funcionamiento del sumador es automático, una de sus entradas es el registro del acumulador AC y la otra entrada es el registro de datos DR, el cual es controlado por la señal de control de resta.

Después de que opera el operador de suma, el resultado se envía al selector 21, junto con los datos que provienen directamente del bus de datos, esperando que se seleccione la señal s21, y luego se almacena en el registro de acumulación de CA de acuerdo con la señal de CA para el próximo cálculo.

zf se usa como un registro de bandera cero, si el resultado almacenado en el acumulador de CA es cero, entonces zf es 1. En este momento, si pre es 1, entonces pc puede configurarse como el valor del registro de datos DR, realizando la función de salto cuando el resultado de la operación es cero. De lo contrario, continúe ejecutando hacia abajo.

Después de la integración general, el código es el siguiente:

def adder(a=0, b=0, ci=0, sub=0):
    return a-b+ci if sub == 1 else a+b+ci
def b8_21selector(a=0, b=0, sel=0):
    return a if sel == 0 else b
def register():
    temp = 0
    def register_inner(data=0, w=0):
        nonlocal temp
        if w == 1:
            temp = data
        return temp
    return register_inner
def int2bin(data=0, length=8, tuple_=1, string=0, humanOrder=0):
    #辅助函数,整数转换为二进制字符串或者元祖。
    r = bin(data)[2:].zfill(length)
    r = r[::-1] if humanOrder == 0 else r
    return r if string == 1 else tuple(int(c) for c in r)
def cpu():
    pc = 0 # pc 计数器从 0 开始,无限循环。
    IR, DR, AC = register(), register(), register() # 新建寄存器
    ramc = [0x18, 0x19, 0x1d, 0x02, 0x31, 0x30, 0x00] # 初始化代码
    ramd = [10, 2, 3, 0xff, 0x06, 0x02] # 初始化数据

    IR(0, w=1) # 初始化寄存器
    DR(0, w=1)
    AC(0, w=1)
    while True:
        IR(ramc[pc], w=1) # 指令读写
        *_, pre, dr, ac, sub, w, s21 = int2bin(IR(), humanOrder=1) # 指令解码
        if IR() == 0:
            break # HLT信号
        DR(ramd[pc], w=dr) # 数据读写
        r = adder(AC(), DR(), sub=sub) # 加法器自动加法
        AC(b8_21selector(DR(), r, s21), w=ac) # 选择器选择后,累加寄存器读写
        ramd[pc] = AC() if w else ramd[pc] # 根据 w 信号,数据写入 RAM
        zf = (AC() == 0) # 零标志位寄存器
        pc = DR() if (pre == 1 and zf == True and s21 == 1) else pc + 1 # Jz 指令跳转
        pc = DR() if (pre == 1 and s21 == 0) else pc # 无条件跳转 Jmp
        print(AC()) 
if __name__ == '__main__':
    cpu()

Se puede ver que el resultado de salida es: 10,12,9,9,9,9,6,6,6,6,3,3,3,3,0,0,0¡el programa funciona normalmente y la CPU funciona!

7. Programación para CPU, experimentando el flujo de trabajo de los antiguos programadores

Escribamos un programa para nuestra CPU de juguete que reste 1 de 5 hasta que sea 0: primero, necesitamos cargar 5, luego restar 1, juzgar si es cero, saltar para detenerse si es cero y continuar saltando a Menos 1 lugar

El código y los datos se escriben por separado:

    ramc = [0x18, 0x1d, 0x31, 0x30, 0x00]
    ramd = [5,    1,    0x04, 0x01]

Salida del programa:

5,4,4,4,3,3,3,2,2,2,1,1,1,0,0

¡funciona bien!

"

Si convertimos estos datos en binarios, obviamente es información de 8 bits, y 8 pequeños agujeros por unidad se graban secuencialmente en la cinta de papel, y luego se puede ejecutar en una computadora antigua. Esto es lo que hizo la primera generación de programadores.

"

8. Resumen

Para comprender el principio de funcionamiento de la CPU, es importante comprender que la PC sigue incrementando su dirección y ejecuta las instrucciones del programa secuencialmente. Cuando se encuentra una instrucción de salto, la PC se restablece a la nueva dirección. En el proceso de ejecución secuencial de las instrucciones del programa, cada paso consiste en analizar las instrucciones del programa, generar señales de control y luego controlar el estado de funcionamiento de todos los dispositivos relacionados con la CPU, generar resultados de cálculo del programa y guardarlos en registros o RAM.

Desde una perspectiva macro, el principio de funcionamiento de la CPU es leer los datos de la memoria, completar el cálculo en la ALU y luego guardarlo en la memoria, y el sistema de entrada y salida completa la interacción con otros periféricos; al principio, lea el registro de instrucciones del programa, luego analice la instrucción y controle el proceso específico de cada componente; desde un punto de vista microscópico, todos los componentes relacionados con la CPU, incluido el contador de programa de PC, la unidad de operación lógica digital ALU y la memoria RAM, son en realidad uno por uno Transistores, estos triodos se encienden o apagan bajo la acción de la corriente y completan todas las funciones de las operaciones lógicas digitales, manteniendo los estados de memoria y generando señales de pulso.

Este artículo crea y simula una CPU desde el nivel meso e implementa una CPU simple de nivel de juguete con 40 líneas de código Python. Le permite completar operaciones de suma y resta, y tiene las funciones de lectura y escritura de memoria, salto y salto condicional. El texto completo es relativamente seco, ¡gracias por leer!

Supongo que te gusta

Origin blog.csdn.net/weixin_69999177/article/details/128541944
Recomendado
Clasificación