Skip to content
Juan Gonzalez-Gomez edited this page Sep 11, 2016 · 53 revisions

ACC1: Apollo CPU Core 1

Ejecutando la primera instrucción

Videoblog 25

Click to see the youtube video

Introducción

Partimos del ACC0, que almacena instrucciones en la memoria ROM y las muestra por los leds. Le añadimos una unidad de control para generar las señales de control necesarias para ejecutar una instrucción: TCF, que hace un salto incondicional.

Además, el ACC1 tiene dos modos de funcionamiento. En el modo manual las instrucciones se ejecutan cada vez que se aprieta el pulsador SW1. En el modo automático, se ejecutan de forma temporizada (cada 350ms por defecto), para poder ver en los leds lo que va ocurriendo.

Con el pulsador SW2 se cambia de un modo a otro. El modo actual se indica por el LED7

Objetivos

  • Tener un core mínimo que pueda ejecutar su primer programa (un bucle infinito que no hace nada todavía)
  • Aprender el formato de las instrucciones del AGC

Formato de instrucciones

Las instrucciones del AGC tiene 15 bits. El formato principal de las instrucciones está formado por dos campos. Los 3 bits más significativos contienen el código de operación (opcode) que determina qué instrucción es. Los 12 siguientes almacenan la dirección de memoria sobre la que actúa la instrucción

Resumen de instrucciones y directivas del ACC1

En las siguientes tablas se resumen las instrucciones y directivas del ensamblador que podemos usar con el ACC1. Según vayamos avanzando en los siguientes cpus (ACC2, ACC3, etc) la iremos ampliando

Directivas del ensamblador

Nombre Descripción Ejemplo
SETLOC k12 Establecer la dirección de la siguiente instrucción. k12 es una dirección de 12 bits en octal SETLOC 4000
OCT k15 Definir un dato de 15 bits, en octal. Para que se almacene en memoria OCT 67705

La directiva SETLOC la usamos para indicar la dirección de comienzo del programa, que siempre es la 4000 en octal (0x800). La directiva OCT nos permite definir datos que se quedan almacenados en la memoria ROM

Instrucciones

Nombre Opcode Descripción Ejemplo Código máquina ej. (octal)
TCF k12 1 Salto incondicional a la dirección k12 TCF loop 10000 + loop
NOOP 1 No hacer nada. Sólo se consumen ciclos de reloj NOOP 10000 + dir_act + 1

La instrucción TCF (Transfer Control Fixed) realiza un salto a la dirección indicada, y continúa ejecutando las instrucciones que hay a partir de esa dirección.

La instrucción NOOP no hace nada, salvo consumir ciclos de cpu. Luego se continúa con la ejecución de la siguiente instrucción. La instrucción NOOP es en realidad la misma que TCF. Lo que se hace es un salto a la siguiente dirección. Si noop se encuentra en la 4000 (en octal), sería equivalente a la instrucción TCF 4001.

Es el ensamblador es que realiza esta sustitución, por lo que la CPU sólo tiene implementada TCF

Descripción del ACC1

ACC1 = ACC0 + Unidad de control + Modo automático

El ACC1 está dividido en la ruta de datos y la unidad de control

Diagrama de bloques

El diagrama de bloques del ACC1 se muestra en la siguiente figura:

Ruta de datos

Se divide en dos partes. Una es la que permite acceder a memoria y leer las instrucciones. La otra es la que implementa los modos de funcionamiento

Acceso a memoria y lectura de instrucciones

Es similar a la del ACC0 salvo que la señal de load del registro G se controla con la señal WG proveniente de la unidad de control, y el registro S tiene una señal de load conectada a la señal de control WS.

Es la señal WS la que permite cargar S con un valor nuevo, y empezar a leer instrucciones desde otra dirección de memoria. Es decir, la señal de control WS es la que permite implementar la instrucción de salto TCF.

La **salida del registro G **está conectada a los leds (los 7 bits más significativos, igual que en el ACC0) para poder ver la instrucción que se está ejecutando o el dato al macenado. Además, los 12 bits de menor peso, correspondientes al campo dirección de la instrucción se llevan hasta el registro S, para cargar la nueva dirección si la instrucción es TCF. Los 3 bits de mayor peso (campo opcode) se introducen en la unidad de control para que ésta sepa qué instrucción se tiene que ejecutar. Si es TCF, se emite la señal WS para cargar la dirección del salto en el registro S. Si es otra intrucción, se ignorará y se pasará a la siguiente

Implementación de los modos de funcionamiento

Se usa un flip-flop tipo T para almacenar el modo de funcionamiento: 0-Automático y 1-Manual. Este flip-flop se cambia de estado al apretar el pulsador SW2

La unidad de control tiene una entrada (next) para indicar cuándo pasar a la siguiente instrucción. Por ella se introduce bien la señal manual proveniente del pulsador SW1 o la señal de un temporizador que emite un pulso cada 350ms (por defecto). Un multiplexor 2 a 1 selecciona una u otra señal según el modo actual

Unidad de control

La unidad de control tiene 2 entradas:

  • opcode: Los 3 bits que indican de qué instrucción se trata
  • next: Indica cuándo se puede pasar a ejecutar la siguiente instrucción. Según el modo de funcionamiento, esta señal proviene bien del pulsador SW1 o bien de un temporizador

A partir de estas entradas, y del estado del controlador, genera las 3 señales de control:

  • WG: Escritura en el registro G (para almacenar la instrucción leída de memoria)
  • WS: Escritura en el registro S (Para leer la instrucción de la dirección indicada)
  • INCS: Incrementar el registro S para leer la instrucción de la siguiente dirección

La unidad de control se implementa mediante una máquina de estados

Diagrama de estados

La unidad de control tiene 4 estados. Por defecto, todas las señales de control están a 0. Inicialmente, en el estado de Fetch, el registro S está direccionando la memoria. En el siguiente cíclo de reloj se pasa al estado READ_OP, donde los datos ya están listos y se activa la señal WG para capturarlos en el registro G. En paralelo, se genera la señal INCS para incrementar la dirección actual y apuntar a la siguiente instrucción

En el siguiente ciclo se pasa al estado de EXEC0 para ejecutar la intrucción. Si se trata de la instrucción TCF, se emite la señal WS para almacenar la nueva dirección desde donde leer la siguiente instrucción. Para el resto de instrucciones no se emite ninguna señal de control y se pasa al siguiente estado.

El último estado es WAIT. El controlador se queda esperando a que se active la señal next que indica cuándo pasar a la siguiente instrucción (e iniciar un ciclo nuevo). Esta señal viene o bien del pulador SW1 o bien del temporizador de 350ms. Cuando se activa next, se comienza un ciclo nuevo pasando a FETCH para leer la nueva instrucción

ACC1 en Icestudio

El ACC1 en Icetudio se encuentra en el fichero ACC1.ice. Una vez abierto, tiene este aspecto:

El módulo MEM contiene la memoria ROM y los registros S y G:

ACC1 en Verilog

El módulo principal es el ACC1. El resto son los mismos que los usados en el ACC0

module ACC1 (
    input wire clk,       //-- System clock
    input wire next,      //-- Process next instruction/data
    input wire selmode,   //-- Toggle Mode: Manual / automatic

    output wire d0,       //-- Output leds
    output wire d1,
    output wire d2,
    output wire d3,
    output wire d4,
    output wire d5,
    output wire d6,
    output wire d7
);

//--------------------------------------------
//-- CONSTANTS
//--------------------------------------------
//-- Constants for the modes: automatic/manual
localparam MANUAL_MODE = 1'b1;
localparam AUTOMATIC_MODE = 1'b0;

//-- Constant for the speed of automatic mode (Number of bits for the timmer)
localparam SLOW = 24;    //--  1.4 secs
localparam MEDIUM = 22;  //--  350 ms
localparam FAST = 20;    //--  90 ms

//-- AGC Opcodes
localparam  TCF = 3'b001;  //-- Transfer Control Fixed. Unconditional jump

//-------------------------------------------------
//-- PARAMETERS & CONFIGURATION
//-------------------------------------------------

//-- Rom file
parameter ROMFILE = "rom.list";

//-- Parameters for the memory
localparam AW = 12;     //-- Address bus
localparam DW = 16;     //-- Data bus

//-- Initial address
localparam BOOT_ADDR = 12'h800;

//-- Initial G-reg value (shown in leds initially)
localparam G_INIT = 15'hAA00;

//-- Default mode configuration (Uncomment one of the options)
//localparam DEFAULT_MODE = AUTOMATIC_MODE;
localparam DEFAULT_MODE = MANUAL_MODE;

//-- Configuration for the timer of the automatic mode
//-- Uncomment one of the options
//localparam AUTOMATIC_MODE_SPEED = SLOW;
localparam AUTOMATIC_MODE_SPEED = MEDIUM;
//localparam  AUTOMATIC_MODE_SPEED = FAST;

//----------------------------------------------------------
//-- ROM MEMORY
//----------------------------------------------------------

//-- ROM output data
wire [DW-1: 0] rom_dout;

//-- Instantiate the ROM memory (2K)
genrom #(
        .ROMFILE(ROMFILE),
        .AW(AW-1),
        .DW(DW))
  ROM (
        .clk(clk),
        .cs(S[AW-1]),         //-- Bit A11 for the chip select
        .addr(S[AW-2:0]),     //-- Bits A10 - A0 for addressing the Rom (2K)
        .data_out(rom_dout)
      );

//-----------------------------------------------------------
//-- INPUT BUTTONS CONFIGURATION
//-----------------------------------------------------------

//-- Configure the pull-up resistors for clk and rst inputs
wire next_p;   //-- Next input with pull-up activated
wire selmode_p; //-- Selmode button with pull-up activated
wire sw1;
wire sw2;

//-- Button 1 pull-up
SB_IO #(
   .PIN_TYPE(6'b 1010_01),
   .PULLUP(1'b 1)
) io_pin (
   .PACKAGE_PIN(next),
   .D_IN_0(next_p)
);

//-- Button 2 pull-up
SB_IO #(
   .PIN_TYPE(6'b 1010_01),
   .PULLUP(1'b 1)
) io_pin2 (
   .PACKAGE_PIN(selmode),
   .D_IN_0(selmode_p)
);

//-- Buttons with positive logic: (1 pressed, 0 not presssed)
assign sw1 = ~next_p;
assign sw2 = ~selmode_p;

//-- switch button debounced
wire sw1_deb;
wire sw2_deb;

//-- Debouncer for button 1
debounce_pulse deb1 (
  .clk(clk),
  .sw_in(sw1),
  .sw_out(sw1_deb)
  );

//-- Debouncer for button 2
debounce_pulse deb2 (
  .clk(clk),
  .sw_in(sw2),
  .sw_out(sw2_deb)
  );

//-- This debouncer is used with the timer for generating a 1-cycle width pulse
//-- Not for debouncing
debounce_pulse deb3 (
  .clk(clk),
  .sw_in(timer_trig),
  .sw_out(timer_trig_pulse)
  );

//------------------------------------
//-- S REGISTER: Addressing memory
//------------------------------------

//-- Define de S-reg
reg  [AW-1: 0] S = BOOT_ADDR;

//-- Register with paralell load (WS) and increment (INCS)
always @(posedge clk) begin
    if (WS)
      S <= dir12;
    else
      if (INCS)
        S <= S + 1;
end

//---------------------------------------------------------
//-- G REGISTER: Store Instruction/data read from memory
//---------------------------------------------------------
//-- Define de G-reg
reg [14:0] G = G_INIT;

//-- G has different fields
wire [2:0]  opcode = G[14:12]; //-- Opcode: 3 bits
wire [11:0] dir12 = G[11:0];   //-- Dir12: 12 bits

//-- Register with parallel load (WG)
always @(posedge clk)
  if (WG)
    G <= rom_dout[14:0];

//-----------------------------------------
//-- LEDS
//-----------------------------------------

//-- The 7 more significant bits of G regs are shown in leds
assign {d6,d5,d4,d3,d2,d1,d0} = G[14:8];

//-- The LED7 is for displaying the mode (automatic/manual)
assign d7 = mode;

//------------------------------------------
//-- Timer for the automatic mode
//------------------------------------------
wire timer_trig;
wire timer_trig_pulse;

prescaler #(
  .N(AUTOMATIC_MODE_SPEED)
) timer_automatic (
  .clk_in(clk),
  .ena(1'b1),
  .clk_out(timer_trig)
);

//-------------------------------------------------
//-- Flip-flip T for toggling the mode
//-------------------------------------------------
reg mode = DEFAULT_MODE;

//-- Flip-flip T. The input is the button 2
always @(posedge clk) begin
//-- Change the mode when the SW2 is pressed
  if (sw2_deb)
    mode = ~mode;
end

//-- Mux for choosing manual/automatic event signal
wire event_trig = (mode == MANUAL_MODE) ? sw1_deb : timer_trig_pulse;

//---------------------------------------------------
//-- CONTROL UNIT
//---------------------------------------------------

//-- fsm states
localparam  FETCH = 0;
localparam  READ_OP = 1;
localparam  EXEC0 = 2;
localparam  WAIT = 3;

//-- Registers for storing the states
reg [1:0] state = WAIT;
reg [1:0] next_state;

//---------------- Control signals
reg WG = 0;    //-- Load the G register
reg INCS = 0;  //-- Increment the S register
reg WS = 0;    //-- Load the S register

//-- Transition between states
always @(posedge clk)
  state <= next_state;

//-- Control signal generation and next states
always @(*) begin

  //-- Default values
  next_state = state;      //-- Stay in the same state by default
  WG = 0;
  INCS = 0;
  WS = 0;

  case (state)

    FETCH: begin
      next_state = READ_OP;
    end

    READ_OP: begin
      WG = 1;   //-- Read the opcode into the G register
      INCS = 1; //-- Increment the S reg
      next_state = EXEC0;
    end

    EXEC0: begin

      //-- If opcode is TCF, load the register S
      if (opcode == TCF)
        WS = 1;

      next_state = WAIT;
    end

    WAIT: begin
      //-- Wait until an event is trigger (timer or sw1 pressed)
      if (event_trig)
        next_state = FETCH;
    end

    default: begin
    end

  endcase

end

endmodule

Configuraciones

En el código Verilog hay una serie de constantes que se pueden cambiar para configurar el ACC1

  • Configuración del valor inicial del registro G

Por defecto, el ACC1 está configurado en modo manual. Arranca en el estado de espera, hasta que se apriete el pulsador y se ejecute la primera instrucción. El valor inicial del registro G sale por los leds. Este valor se usa para indicar que está esperando (y que no se ha ejecutado ninguna instrucción todavía). El valor asignado es el 0xAA00, que hace que se alternen los leds encendidos con los apagados:

//-- Initial G-reg value (shown in leds initially)
localparam G_INIT = 15'hAA00;

Podemos cambiar este valor para que se enciendan los leds que queramos (ej. todos: 0x7F00, ninguno: 0x0000, etc)

  • Configurar Modo de funcionamiento por defecto

Por defecto, el ACC1 está en modo manual. Esto se controla con la constante DEFAULT_MODE:

//-- Default mode configuration (Uncomment one of the options)
//localparam DEFAULT_MODE = AUTOMATIC_MODE;
localparam DEFAULT_MODE = MANUAL_MODE;

Si le asignamos el valor AUTOMATIC_MODE, al arrancar entrará directamente en modo automático

  • Configuración de la velocidad en modo automático:

Hay predefinidas tres velocidades: SLOW, MEDIUM y FAST que se corresponden con periodos de 1.4seg, 350ms y 90ms respectivamente. Nos permiten determinar el tiempo que de esperar hasta ejecutar la siguiente instrucción (y así poder ver los leds). Por defecto la velocidad está en MEDIUM, pero esto se puede cambiar con la constante AUTOMATIC_MODE_SPEED:

//-- Configuration for the timer of the automatic mode
//-- Uncomment one of the options
//localparam AUTOMATIC_MODE_SPEED = SLOW;
localparam AUTOMATIC_MODE_SPEED = MEDIUM;
//localparam  AUTOMATIC_MODE_SPEED = FAST;

Programas de pruebas

test1.ags

Este programa almacena datos a patir de la dirección de arranque 4000 (en octal) y al final realiza un salto a la dirección inicial (40000), definida por la etiqueta loop1.

	SETLOC 4000

loop1	OCT 40000  #-- 100 0000  Shown in leds
loop2	OCT 60000  #-- 110 0000
loop3	OCT 70000  #-- 111 0000
loop4	OCT 74000  #-- 111 1000
loop5	OCT 76000  #-- 111 1100
loop6	OCT 77000  #-- 111 1110
loop7	OCT 77400  #-- 111 1111

	#-- Change the TCF destination jump for testing: loop1, loop2, loop3...
loop8	TCF loop1    #-- 0001000

El ACC1 lee la primera palabra y muestra los 7 bits más significativos por los leds. Como es una instrucción no conocida, no se ejecuta nada, y se pasa a la siguiente. Se hace así con todos los datos hasta llegar a la instrucción TCF loop1, que realiza un salto a la dirección inicial, por lo que la secuencia se vuelve a repetir

Si modificamos el programa y cambiamos el salto a loop2 (en vez de a loop1), veremos cómo la segunda vez la secuencia comienza por ese valor. Esto lo hacemos para comprobar que efectivamente la instrucción de salto funciona correctamente

test2.ags

Este es un programa de prueba para comprobar que la instrucción NOOP funciona. Esta instrucción en realidad es una instrucción TCF a la siguiente instrucción en memoria

        SETLOC 4000
        NOOP
        NOOP
loop    OCT 63400
        NOOP
        OCT 77777
        TCF loop

Inicialmente se ejecutan dos instrucciones NOOP seguidas y luego se entra en un bucle infinito donde hay un NOOP y dos datos. Se puede ver que el código de operación de NOOP y TCF LOOP es el mismo

test3.ags

Este es el primer programa que se puede ejecutar en un AGC real. En nuestro caso lo podremos ejecutar en el ACC1 y simularlo en un AGC virtual con la herramienta yaAGC

       SETLOC 4000  

loop   NOOP
       TCF loop

Los anteriores no se pueden simular, porque se incluyen datos (con OCT) que el procesador lee como instrucciones, y por tanto tendrá un comportamiento extraño si se ejecutan. En el ACC1 se ejecutan correctamente porque se ignoran.

Sin embargo, test3.ags sí lo podemos simular. Al ejecutarlo en el ACC1 veremos que siempre se quedan encendidos los leds 3 y 4 (Ya que los 7 bits más significativos de NOOP y TCF loop son iguales). Por eso hemos usado los ejemplo test1.ags y test2.ags, para poder ver actividad en los leds.

Ensamblado y generación de la rom

Los programas se ensamblan igual que hicimos con el ACC0. Por ejemplo, para ensamblar test1.ags y generar el fichero rom.list con el código máquina ejecutamos:

$ yaYUL test1.ags
[...]
$ acc-rom.py test1.ags.bin
[...]

Ahora ya podemos sintetizar y cargar el ACC1 (bien usando apio en la línea de comandos o bien icestudio gráficamente)

Simulación con yaAGS

El ejemplo test3.ags lo podemos simular con la herramienta **yaAGC **. El simulador nos será muy útil para comprobar que nuestros programas hacen lo que queremos que hagan, antes de probarlos en el ACC sintetizado

Para simular test3.ags seguimos los siguentes pasos:

$ yaYUL test3.ags
[...]
$ $ yaAGC --exec=test3.ags.bin
Apollo Guidance Computer simulation, ver. , built Aug  8 2016 10:58:50
Copyright (C) 2003-2009 Ronald S. Burkey, Onno Hommes.
yaAGC is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Refer to http://www.ibiblio.org/apollo/index.html for additional information.
(agc) 

Tras ejecutar YaAGC aparece el prompt (agc) donde podemos escribir comandos para simular

Todos los comandos se pueden listar con el comando help all.

Nosotros usaremos los siguientes:

  • Mostrar el programa que estamos simulando:
(agc) list 1
1	# -- Hello world example that can be both: executed in ACC1 and 
2	# -- simulated in yaAGC
3	# -- The program is just an infinite loop that do nothing
4	# -- To assemble: ./yaYUL test3.ags
5	# -- To simulate: ./yaAGC  --exec=test3.ags.bin
6	
7		SETLOC 4000  
8	
9	loop	NOOP
10		TCF loop
  • Simular la primera instrucción:

Introducimos el comando step y el AGC ejecutará la primera instrucción (NOOP) y luego nos mostrará la siguiente instrucción

(agc) step
loop () at test3.ags:10
10		TCF loop
  • Simular la siguiente instrucción:

Si introducimos de nuevo step, se ejecuta la instrucción de salto (TCF) y vemos que la siguiente instrucción es otra vez el NOOP de la línea 9:

(agc) step
9	loop	NOOP
(agc)
  • Simulando el resto de instrucciones:

Como está en un bucle infinito, siempre se ejecutarán las instrucciones de la línea 9 y 10

(agc) step
10		TCF loop
(agc) step
9	loop	NOOP
(agc) step
10		TCF loop
(agc) step
9	loop	NOOP
[...]
  • Terminando la simulación

Para terminar ejecutamos el comando quit:

(agc) quit

Instrucciones de uso

Por defecto, la rom del ACC1 contiene el programa test1.ags ya ensamblado. De forma que si no se ensambla nada, ese será el programa por defecto que se cargará en el ACC1

Instalación de las herramientas

Instalar todas las herramientas y clonar el repositorio del ACC: como se indica en las intrucciones del ACC0

Sintetizando y cargando con icestudio

  • Arrancar el icestudio
  • Abrir el proyecto ACC1: ACC/hw/roadmap/11-ACC1/ACC1.ice
  • Conectar la placa Icezum Alhambra
  • Ejecutar la opción Tools/Upload

Sintetizando y cargando con Apio (línea de comandos)

  • Entrar en el directorio ACC/hw/roadmap/11-ACC1/
cd ACC/hw/roadmap/11-ACC1/
  • Conectar la tarjeta Icezum Alhambra
  • Ejecutar los siguientes comandos:
$ apio clean
[...]
$ apio upload

Probando el ACC1

Nada más configurar la FPGA (con el programa test1.agc cargado), aparecerá el valor inicial del registro G en los leds:

El led7 está activado, indicando que estamos en el modo manual. Al apretar una vez el pulsador SW1 se muestra la primera instrucción (Que es dato colocado en la dirección 0x800):

Cada vez que pulsemos SW1, aparecerá el siguiente dato. Iremos viendo en los leds la secuencia creada, en la que se van encendiendo todos los leds, de uno en uno. En el séptimo click de SW1 estarán todos los leds encendidos:

Al hacer un nuevo click en SW1 se mostrará la instrucción TCF:

Y al hacer un nuevo click volveremos al principio, mostrando el dato de la dirección inicial (0x800), repitiéndose el bucle.

Si en cualquier momento apretamos SW2, pasaremos al modo automático, donde las instrucciones se irán ejecutando una tras otras, cada 350ms. El led7 estará apagado, indicando que estamos en modo automático

Si volvemos a apretar SW2, pasaremos de nuevo al modo manual.

Vídeo

La demostración del funcionamiento del ACC1 en la Icezum Alhambra se puede ver en este vídeo de youtube:

Click to see the youtube video

Primero, en el modo manual, se aprieta sucesivamente el pulsador SW1 para ver los valores y observar cómo se vuelve al comienzo al ejecutarse la instrucción TCF. Luego se aprieta SW2 para pasar a modo automico y ver la secuencia sin tener que apretar ningún botón. Finalmente se vuelve al modo manual

Ejercicios propuestos

  1. Modificar el programa test1.ags para que el salto no se haga a la etiqueta loop1, sino a alguna de las anteriores. Sintetizar, cargar y comprobar que se ejecuta el salto correctamente a la nueva dirección

  2. Hacer un programa nuevo que genere la secuencia del "Coche fantástico" en los leds: que la luz primero vaya hacia un lado y luego hacia otro. Sintetizarlo, cargarlo y comprobar que funciona correctamente

Sobre esta página

Todas las figuras están hechas con la herramienta libre Inkscape, en formato SVG

Las figuras 3D están hechas con la herramienta libre FreeCAD

Las fuentes de todas las figuras están disponibles en este repositorio