Skip to content
Juan Gonzalez-Gomez edited this page Sep 9, 2016 · 48 revisions

ACC2: Apollo CPU Core 2

Almacenando datos en el registro A

Introducción

Partimos del ACC1 al que le añadimos el registro A de 15 bits y la instrucción CA para cargar datos en él. Los 7 bits menos significativos del registro A se sacan por los leds (en vez de los bits del registro G)

Objetivos

  • Aprender el funcionamiento de las instrucciones que acceden a memoria para leer datos

Resumen de instrucciones y directivas del ACC2

En las siguientes tablas se resumen las instrucciones y directivas del ensamblador que podemos usar con el ACC2

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 una constante de 15 bits, en octal,para que se almacene en memoria ROM OCT 67705
DEC k15 Definir una constante de 15 bits, en decimal, para que se almacene en memoria ROM DEC 1023
label1 EQUALS label2 Definir la etiqueta label1 con valor igual a label2 DATA1 EQUALS DATA2
label1 = label2 Definir la etiqueta label1 con valor igual a label2 DATA1 = DATA2

Se han añadido 3 directivas nuevas. La directiva DEC que nos permite crear una constante en decimal, en la memoria ROM. Es igual que OCT, pero especificando el dato en decimal

La directiva EQUALS es equivalente al símbolo =. Sirve para crear una etiqueta igual a otra previamente definida

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
CA k12 3 Cargar el dato de la dirección k12 en el registro A CA dato 30000 + dato

La instrucción CA (Clear and Add) lee un dato almacenado en la dirección indicada y lo carga en el registro A, dejándolo accesible para trabajar con él.

Descripción del ACC2

ACC2 = ACC1 + Registro A + Instrucción CA

Diagrama de bloques

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

Ruta de datos

Partimos de la ruta de datos del ACC1 y añadimos los nuevos elementos.

La salida de dato de la ROM se conecta al registro G (como antes) y al nuevo registro A (de 15 bits). Cuando la señal de control WA se activa, el regitro A captura los datos (Necesario para la ejecución de la instrucción CA)

El direccionamiento de la memoria ROM (12 bits) ahora se puede hacer desde dos registros diferentes: S ó G, dependiendo de la configuración de la señal de control SelSG del multiplexor. Cuando SelSG=0, se lee la dirección del registro S. Así es como se hacía siempre en el ACC1. Esto permite la lectura de instrucciones. Cuando SelSG=1, se usa el registro G, lo que permite acceder a la ROM para leer datos

Los 7 bits menos significativos del registro A se sacan por los leds, para poder visualizar los valores cargados en el registro A (y comprobar que la instrucción CA está funcionando correctamente)

El resto de la ruta de datos es exactamente igual que la del ACC1

Unidad de control

La unidad de control es la misma que la del ACC0, pero incluyen dos señales nuevas WA y SelGS

  • WA: Esta señal se activa para cargar datos en el registro A, y poder implementar con ella la instrucción CA
  • SelSG: Permite seleccionar qué registro se usará para direccionar la memoria. Cuando se están leyendo instrucciones, se usa la dirección proveniente de S. Cuando se leen datos para cargarlos en el registro A (instrucción CA) se debe usar la dirección del registro G

Resumen de las señales de control

Nombre Descripción
WA (Write A) Cargar el registro A
WS (Write S) Cargar el registro S
WG (Write G) Cargar el registro G
INCS (Increment S) Incrementar el registro S en una unidad
SelSG (Select S or G) Seleccionar qué registro (S ó G) se usará para direccionar la memoria

Diagrama de estados

La unidad de control del ACC2 se implementa con un autómata finito de 5 estados. En el estado FETCH, todas las señales de control están a 0, por lo que se usa el registro S para direccionar la memoria ROM. En el siguiente ciclo se pasa al estado READ_INST para leer la instrucción, que se almacena en el registro G, y se incrementa el registro S para apuntar a la dirección de la siguiente instrucción. En el siguiente ciclo se entra en el estado EXEC de ejecución. Si el código de operación, almacenado en G, es igual al de la instrucción TCF, se carga en el **registro S **la nueva dirección y se pasa al estado de WAIT. Si el código de oepración se corresponde con la instrucción CA, se selecciona el registro G para direccionar la memoria ROM (en esa dirección es donde se encuentra el dato a cargar en A). En el siguiente ciclo el dato ya está disponible y se pasa al estado EXEC_CA, donde se guarda en el registro A. En el siguiente ciclo se pasa a WAIT, concluyendo la ejecución de la instrucción CA

ACC2 en Icestudio

El proyecto se encuentra en el fichero ACC2.ice. Se ha utilizado el icestudio 0.2.1-dev. Se ha dividido en sus tres componentes básicos: la ruta de datos, unidad de control y la consola de entrada

  • Diagrama principal:

  • Ruta de datos:

Como icestudio 0.2.1-dev todavía no soporta buses, el cableado es un poco complicado. Y sobre todo hacer modificaciones:

En los próximos ACCs la ruta de datos se implementará en icestudio con un bloque de código, que incluya todo el código verilog, en vez de dividirlo en subpartes que compliquen el cableado

  • Consola de entrada:

Es la misma que en el ACC1, pero se ha metido en un bloque separado para que el proyecto principal quede más limpio:

ACC2 en Verilog

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

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

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

//--------------------------------------------
//-- 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
localparam  CA  = 3'b011;  //-- Clear and Add. Load the A register from memory

//-------------------------------------------------
//-- 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
localparam G_INIT = 15'b0;

//-- Initial A-reg value (shown in leds initially)
localparam A_INIT = 15'h0055;

//-- 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;


//-- ROM address wire
wire [AW-1:0] addr;

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

//-- Addr multiplexer. Select where the addres come from
//-- SelSG == 0 --> From the S register
//-- SelSG == 1 --> From the G register
assign addr = (SelSG == 1) ? G[11:0] : S;

//-----------------------------------------------------------
//-- 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];

//------------------------------------------
//-- A REGISTER
//------------------------------------------
reg [14:0] A = A_INIT;

always @(posedge clk) begin
  if (WA)
    A <= rom_dout[14:0];
end

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

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

//-- Show the 7 less significant bits of the A reg in the leds
assign {d6, d5, d4, d3, d2, d1, d0} = A[6:0];

//-- The LED7 is for displaying the mode (automatic/manual)
assign d7 = mode;
//assign d7 = A[7]; //-- Debug

//------------------------------------------
//-- 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_INST = 1;
localparam  EXEC0 = 2;
localparam  EXEC1 = 3;
localparam  WAIT = 4;

//-- Registers for storing the states
reg [2:0] state = WAIT;
reg [2: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
reg WA = 0;    //-- Load the A register
reg SelSG = 0; //-- Address multiplexer selection

//-- 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;
  WA = 0;
  SelSG = 0;

  case (state)

    FETCH: begin
      next_state = READ_INST;
    end

    READ_INST: 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
      case (opcode)
        TCF: begin
          WS = 1;
          next_state = WAIT;
        end

        CA: begin
          SelSG = 1;
          next_state = EXEC1;
        end

        default: begin
          next_state = WAIT;
        end
      endcase
    end //-- EXEC0

    EXEC1: begin
      if (opcode == CA) begin
        WA = 1;
        SelSG = 1;
        next_state = WAIT;
      end
    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

Además de las mismas configuraciones que el ACC1, se puede cambiar el valor inicial del registro A:

//-- Initial A-reg value (shown in leds initially)
localparam A_INIT = 15'h0055;

Programas de pruebas

test4.ags

Este programa almacena consecutivamente en el registro A diferentes valores cargados de memoria.

En el ACC2, los 7 bits menos significativos del registro A están conectados a los leds, por lo que este programa muestra una secuencia de valores por los leds

	SETLOC 4000

loop1	CA val1   #-- Load value store in valx address
        CA val2  
        CA val3 
        CA val4
        CA val5
        CA val6
        CA val7   
        TCF loop1
                   #-- Mostrado por los leds
val1    OCT 00100  #-- 1 000 000
val2    OCT 00040  #-- 0 100 000
val3    OCT 00020  #-- 0 010 000
val4    OCT 00010  #-- 0 001 000
val5    OCT 00004  #-- 0 000 100
val6    OCT 00002  #-- 0 000 010
val7    OCT 00001  #-- 0 000 001

Una vez cargado el val7, el programa salta al comienzo, repitiendo la secuencia en un bucle infinito

Test5.ags

Ejemplo del uso de la directiva DEC para almacenar un dato definido en decimal. Se cargan 2 valores decimales en el registro A, consecutivamente. Se repite en un bucle infinito. En los leds se muestra una secuencia de 2 estados

        SETLOC 4000

loop    CA val1      
        CA val2
        TCF loop

#-- Datos definidos en decimal
val1    DEC 15    # -- 0x000F
val2    DEC 255   # -- 0x00FF

Test6.ags

Ejemplo de uso de las directivas EQUALs y =

Es el mismo ejemplo que test5.ags, pero se definen las direcciones DATA1 y DATA2 que son iguales a val1 y val2 respectivamente. Las dos directivas son equivalentes

#-- Declarar DATA1 y DATA2 como sinónimos de las direcciones de val1 y val2
#-- respectivamente
DATA1 EQUALS val1
DATA2 = val2

        SETLOC 4000

loop    CA DATA1  #-- Cargar val1 en A
        CA DATA2  #-- Cargar val2 en A
        TCF loop

#-- Data define in decimal
val1    DEC 255
val2    DEC 63

Ensamblado y generación de la rom

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

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

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

Simulación con yaAGS

Los tres ejemplos se pueden simular con yaAGS

Para simular test4.ags seguimos los siguentes pasos:

$ yaYUL test4.ags
[...]
$ yaAGC --exec=test4.ags.bin
[...]
(agc) 

Mostramos las primeras líneas del programa, para comprobar que es el que queremos simular, usando el comando list 1:

(agc) list 1
1	#-- ACC2 test
2	#-- Testing the CA insruction
3	#-- ACC2 will load the A register with different values
4	#-- in ACC2 The 7 less significant bytes of the A reg
5	#--   are always shown in the leds
6	
7		SETLOC 4000
8	
9	loop1	CA val1   #-- Load value store in valx address
10		CA val2  
(agc)

Ahora mostrarmos el contenido de todos los registros, con info registers:

(agc) info registers
A       0x0000  0
L       0x0000  0
Q       0x0000  0
EB      0x0000  0
FB      0x0000  0
Z       0x0800  2048
BB      0x0000  0
ARUPT   0x0000  0
LRUPT   0x0000  0
QRUPT   0x0000  0
ZRUPT   0x0000  0
BBRUPT  0x0000  0
BRUPT   0x0000  0
CHAN07  0x0000  0
CYR     0x0000  0
SR      0x0000  0
CYL     0x0000  0
EDOP    0x0000  0
INDEX   0x0000  0
EXTEND  0x0000  0
IRQMSK  0x0000  0
ISR     0x0000  0
(agc)

El primero de todos es el registro A, que tiene inicialmente el valor 0

Simulamos la primera instrucción con step y vemos los registros:

(agc) step
loop1 () at test4.ags:10
10		CA val2  
(agc) info registers
A		0x0040	64

Ahora A contiene el valor 0x40 hexadecimal (que se corresponde con el 100 en octal o 64 en decimal)

Damos un paso más de simulación y visualizamos otra vez:

(agc) step
11   CA val3 
(agc) info registers
A    0x0020  32

Ahora el registro A ha vuelto a cambiar, y contiene el nuevo valor cargado (40 en octal)

Podemos seguir ejecutando la simulación paso a paso, podemos ejecutar por ejemplo 5 pasos de golpe, con step 5:

(agc) step 5
16		TCF loop1
(agc) info registers
A		0x0001	1

Vemos que ha ejecutado hasta la ultima instrucción, de salto, y que el valor del registro A es 1. Si hacemos un paso más de simulación se ejecuta el salto y volvemos al comienzo:

(agc) step
9  loop1  CA val1
(agc) info registers
A  0x0001  1

El registro A no ha cambiado, porque al ejecutarse la instrucción TCF no se modifica el registro A

Instrucciones de uso

Mismas que para el ACC1

Probando el ACC2

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

Igual que en el ACC1, el led7 está activado indicando que estamos en el modo manual. Al apretar una vez el pulsador SW1 se ejecuta la primera instrucción, que carga el valor 00100 (octal) en el registro A y los muestra por los leds:

Si apretamos nuevamente el pulsador SW1, se ejecuta la siguiente instrucción, que carga el valor 00040 (octal) en el registro A (y lo muestra por los leds)

Cada vez que apretamos el pulsador SW ejecutamos la siguiente instrucción. Al llegar a la séptima pulsación, se carga el valor 00001 en el registro A

En la siguiente pulsación, se ejecuta la instrucción TCF, que no modifica el registro A y por tanto los leds no cambian. En la siguiente pulsación se carga en A el valor inicial, volviendo a comenzar la secuencia.

Igual que en el ACC1, si apretamos el SW2 pasamos al modo automático, donde se van ejecutando las instrucciones una detrás de otra

Vídeo

La demostración del funcionamiento del ACC2 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 ejecutar instrucciones y ver los valores del registro A. Cuando se ejecuta la instrucción TCF vuelve al comienzo. Luego se aprieta SW2 para pasar a modo automico y ver la secuencia sin tener que apretar ningún botón.

Ejercicios propuestos

  1. Modificar el programa test4.ags para que se muestre la misma secuencia la primera vez, pero que a partir de las siguientes no lo haga desde el principio, sino a partir de que el led4 está encendido

  2. Hacer un programa nuevo que genere saque los numeros 5, 17, 63 y 120 (definidos en decimal) por los leds, repitiéndose la secuencia

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