Implementación de la arquitectura Zetta – PASO 4


Síntesis del diseño

Vamos a tomar nuestros (diagramas de bloques y archivos en VHDL)  y a partir de ellos vamos obtener una descripción de todo nuestro sistema totalmente en VHDL, Como? en Altium Designer:

Usamos la opción Compilar proyecto

compilePrj

Si por alguna extraña razón Llorón obtienes un error de la forma “Port PortName not matched to Sheet-Entry at Location”, configura el proyecto para ignorar estos errores (suena a trampa, y lo es!) en el menú Project + Project Options, en la pestaña Error Reporting desactiva busca el ítem Port not linked to parent sheet-symbol y a su modo de reporte donde dice Fatal Error ajústalo a No Report. En mi caso: luego de compilar, guardar, cerrar, y volver a ajustar esta propiedad a la normalidad (Fatal Error), el problema dejo de presentarse en las compilaciones Sonrisa.

Fixbug

Añade un nuevo Constraint File haciendo click derecho en el proyecto.

AddConstraintFile

Yo añadí la línea al final del archivo para indicar la FPGA que voy a usar.

Record=Constraint | TargetKind=Part | TargetId=EP2C20F484C7

La cual también es posible usando un cuadro de dialogo mas amigable mediante el menú Design + Add/Modify Constraint + Part…

fpgaSel

Luego de compilar exitosamente usa el comando Síntesis haciendo click derecho en el proyecto.

Luego de esto puedes ver los archivos generados por el comando de sintesis como se muestra en esta figura.

VhdlGenerated

En estos archivos generados podemos ver como Altium ha hecho escrito todo el cableado por nosotros. Ahora cono el sistema totalmente en VHDL podemos utilizar cualquier herramienta de la industria para continuar nuestro trabajo (simuladores, sintetizadores para FPGA, compiladores de diseño, etc).

La síntesis también genera un archivo de extension .EDN usualmente llamado netlist EDIF, este archivo contiene nuestro diseño convertido a primitivas lógicas pero esa es otra historia.

Simulación

En este paso vamos a realizar una simulación de nuestro procesador usando el software MentorGraphics ModelSim.

Este software es de pago, pero podemos usar una edición gratuita (que puedes descargar aquí) para nuestros propósitos preparada por el fabricante Altera formalmente llamada ModelSim-Altera Starter Edition yo uso la mas reciente al momento de escribir esto (v6.6d for Quartus II v11.0SP1).

  • Debemos crear un nuevo proyecto en ModelSim usando File+New Project…
  • Ajusta el directorio a una carpeta nueva para no polucionar un solo folder.
  • Añade al proyecto todos los archivos VHD que están en la carpeta del proyecto de Altium, preferiblemente como una referencia y no copiando los archivos, Y RECUERDA AÑADIR TAMBIEN LOS ARCHIVOS VHD GENERADOS. Generalmente estan en la carpeta ProjectOutputs/
  • Usa Compile+Compile All.  Todos los archivos deben compilar sin problemas.
  • Usando ModelSim abre cualquier archivo VHD en el proyecto.
  • Con el foco en el editor de código, aparece un menú llamado Source. Úsalo y activa en el la opción Show Language Templates
  • En el panel que aparece llamado Language Templates, usa la opción Create Testbench.
  • En el cuadro de dialogo selecciona la unidad de diseño Entidad sys que esta dentro del Library work. presiona Next y Finish.

Verás que ModelSim ha creado un nuevo archivo llamado sys_tb.vhd y lo ha añadido al proyecto. En el no hay mas que una entidad sin entradas ni salidas que instancia a la entidad sys y le cablea todas su señales pero que no les asigna ningún valor.

Este archivo se llama unTESTBENCH, es un archivo en el que se instancian las unidades de diseño que vamos a someter a prueba y les cableamos y les asignamos señales que cambian en el tiempo para poder ver como se comportan. OJO: la clave entre un archivo de diseño para sintesis y uno para testbench es que en el ultimo normalmente no hay entradas ni salidas y las señales cambian en el tiempo. Por eso es común encontrar partículas wait o after en el código de un testbench.

Añade esto al cuerpo de la arquitectura en el nuevo sys_tb.vhd:

clk <= not clk after 50 ns; -- Generacion de CLK

resetgen: process  -- Generacion de RESET begin reset <= '1'; wait for 300 ns; reset <= '0'; wait; end process;

También inicializa la señal clk en 0 añadiendo := ‘0’ a la declaración de la señal, la línea debería quedarte así:

  SIGNAL clk   :  std_logic := '0' ; 

Compila este archivo del proyecto haciendo click derecho sobre el y escogiendo Compile+Compile Selected.

Aquí les muestro como va mi pantalla.

screenModelSim

Para simular escoge la pestaña Library (en la imagen esta donde queda) y selecciona dentro de la library work la entidad sys_tb, esta entidad corresponde al testbench (banco de prueba) que acabamos de crear. Dale click derecho y enseguida Simulate. La pantalla cambiará drásticamente para mostrarnos el entorno de simulación.

Configura la simulación como puedes ver en la imagen en el orden en que se muestra.

LaunchSimulation

Anuncios

Implementación de la arquitectura Zetta – PASO 3


Ahora que ya conocemos los detalles de que hace cada bloque en el diagrama de la arquitectura, procedemos a bajar un poco mas en el nivel de abstracción y entonces implementaremos cada uno de los bloques necesarios usando VHDL.

Bloque I_IRam – IRam.vhd

--------------------------------------------------------------------------------
-- SubModule IRam
-- Created   12/09/2011 11:03:47 a.m.
--------------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.std_logic_unsigned.all;

entity IRam is port
   (
     address        : in    std_logic_vector(31 downto 0);
     data           : out   std_logic_vector(31 downto 0)
   );
end IRam;
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
architecture rtl of IRam is
    -- Tipos para la memoria ROM
    subtype word_t is std_logic_vector(31 downto 0);
    type memory_t is array(0 to 255) of word_t;

    -- funcion para inicializar la memoria
    function init_rom
        return memory_t is
        variable tmp : memory_t := (others => (others => '0'));
        variable s : std_logic_vector(7 downto 0);
    begin
    		-- Inicializa la memoria
        -- Por ahora solo vamos a inicializar contenido "basura", cuando
        -- estemos usando el procesador sera necesario cambiar esta
        -- inicializacion por un programa verdadero (secuencia de instrucciones
        -- de nuestro procesador Zetta)
        for addr_pos in 0 to 255 loop
            s := std_logic_vector(to_unsigned(addr_pos, s'length));
            tmp(addr_pos) := s & s & s & s; -- uso esto solo para probar
        end loop;
        return tmp;
    end init_rom;

    -- Declara la señal con el tipo de memoria y asigna una funcion
    -- de inicializacion, Quartus II creara un archivo (.mif)
    -- para esta memoria basado en esta inicializacion
    signal rom : memory_t := init_rom;

begin
	-- retorna la posicion de memoria
  data <= rom(conv_integer(address));
end rtl;
--------------------------------------------------------------------------------

Bloque I_UnpackInst – UnpackInst.vhd

--------------------------------------------------------------------------------
-- SubModule UnpackInst
-- Created   12/09/2011 11:04:05 a.m.
--------------------------------------------------------------------------------
library IEEE;
use IEEE.Std_Logic_1164.all;

entity UnpackInst is port
   (
     INST         : in    std_logic_vector(31 downto 0);
     OPCODE       : out   std_logic_vector(5 downto 0);
     RD           : out   std_logic_vector(4 downto 0);
     RS1          : out   std_logic_vector(4 downto 0);
     RS2          : out   std_logic_vector(4 downto 0);
     IMM          : out   std_logic_vector(15 downto 0);
     VAL          : out   std_logic_vector(25 downto 0)
   );
end UnpackInst;
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
architecture Structure of UnpackInst is
begin
     opcode <= inst(31 downto 26);
     rd <= inst(25 downto 21);
     rs1 <= inst(20 downto 16);
     rs2 <= inst(15 downto 11);
     imm <= inst(15 downto 0);
     val <= inst(25 downto 0);
end Structure;
--------------------------------------------------------------------------------

Bloque I_RegFile – RegFile.vhd

--------------------------------------------------------------------------------
-- SubModule RegFile
-- Created   12/09/2011 11:04:13 a.m.
--------------------------------------------------------------------------------
library IEEE;
use IEEE.Std_Logic_1164.all;
use IEEE.std_logic_unsigned.all;

entity RegFile is port
   (
     rd_data         : in    std_logic_vector(31 downto 0);
     rd_wrl           : in    std_logic;
     rd_wrh           : in    std_logic;
     rd              : in    std_logic_vector(4 downto 0);
     rs1             : in    std_logic_vector(4 downto 0);
     rs2             : in    std_logic_vector(4 downto 0);
     rs1_data        : out   std_logic_vector(31 downto 0);
     rs2_data        : out   std_logic_vector(31 downto 0);
     clk             : in    std_logic
   );
end RegFile;
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
architecture Structure of RegFile is
  type regs_t is array(0 to 31) of std_logic_vector(15 downto 0);
  signal regsh : regs_t;
  signal regsl : regs_t;
begin

     process(clk)
     begin
     if rising_edge(clk) then
        -- Engancha la parte baja
        if rd_wrl='1' then
          regsl(conv_integer(rd)) <= rd_data(15 downto 0);
        end if;
        -- Engancha la parte alta
        if rd_wrh='1' then
          regsh(conv_integer(rd)) <= rd_data(31 downto 16);
        end if;
     end if;
     end process;

    -- Muxes
    rs1_data <= regsh(conv_integer(rs1)) & regsl(conv_integer(rs1));
    rs2_data <= regsh(conv_integer(rs2)) & regsl(conv_integer(rs2));

end Structure;
--------------------------------------------------------------------------------

Bloque I_Alu – Alu.vhd

--------------------------------------------------------------------------------
-- SubModule Alu
-- Created   12/09/2011 11:04:20 a.m.
--------------------------------------------------------------------------------
library IEEE;
use IEEE.Std_Logic_1164.all;
use IEEE.std_logic_unsigned.all;
use ieee.std_logic_arith.all;

entity Alu is port
(
  A           : in    std_logic_vector(31 downto 0);
  B           : in    std_logic_vector(31 downto 0);
  R           : out   std_logic_vector(31 downto 0);
  FLAGS       : out   std_logic_vector(2 downto 0);
  OP          : in    std_logic_vector(3 downto 0)
);
end Alu;
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
architecture Structure of Alu is

  signal rres : std_logic_vector(31 downto 0);
begin

  process(a, b, op)
  begin
  case conv_integer(op) is
    when 0 => rres <= a + b;
    when 1 => rres <= a - b;
    when 2 => rres <= CONV_STD_LOGIC_VECTOR(conv_integer(a * b), 32);
    when 3 => rres <= a and b;
    when 4 => rres <= a or b;
    when 5 => rres <= not a;
    when 6 => rres <= shr(a, b);
    when 7 => rres <= shl(a, b);
    when others => rres <= a;
  end case;
  end process;

  r <= rres;
  flags(0) <= '1' when rres = 0 else '0';
  flags(1) <= '1' when rres > 0 else '0';
  flags(2) <= '1' when rres < 0 else '0';
end Structure;
--------------------------------------------------------------------------------

Bloque I_Mux – Mux.vhd

--------------------------------------------------------------------------------
-- SubModule Mux
-- Created   12/09/2011 11:04:29 a.m.
--------------------------------------------------------------------------------
library IEEE;
use IEEE.Std_Logic_1164.all;
use ieee.std_logic_unsigned.all;

entity Mux is port
   (
     a         : in    std_logic_vector(15 downto 0);
     b         : in    std_logic_vector(31 downto 0);
     o         : out   std_logic_vector(31 downto 0);
     sel       : in    std_logic_vector(1 downto 0);
     c         : in    std_logic_vector(31 downto 0)
   );
end Mux;
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
architecture Structure of Mux is
begin
  process(a,b,c, sel)
  begin
  case conv_integer(sel) is
  	when 0 => o <= a & a; -- "&" significa concatenar, NO es la operacion AND
    when 1 => o <= b; -- seleccion de b
    when others => o <= c; -- seleccion de c
  end case;
  end process;
end Structure;
--------------------------------------------------------------------------------

Bloque I_PC – PC.vhd

--------------------------------------------------------------------------------
-- SubModule PC
-- Created   12/09/2011 11:10:36 a.m.
--------------------------------------------------------------------------------
library IEEE;
use IEEE.Std_Logic_1164.all;
use ieee.std_logic_unsigned.all;

entity PC is port
   (
     pc         : out   std_logic_vector(31 downto 0);
     inc        : in    std_logic_vector(25 downto 0);
     jump       : in    std_logic_vector(1 downto 0);
     clk        : in    std_logic;
     reg        : in    std_logic_vector(31 downto 0);
     reset      : in    std_logic
   );
end PC;
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
architecture Structure of PC is
   signal rpc : std_logic_vector(31 downto 0);
begin
     process(clk, reset)
     begin
     if reset = '1' then
     -- Usamos reset asincrono para volver a cero
        rpc <= (others => '0');
     elsif rising_edge(clk) then
     -- En el flanco de subida realizamos el salto si es requerido o
     -- incrementamos en 1 si no es necesario el salto
        case(conv_integer(jump)) is
        when 0 => rpc <= rpc + 1; -- incremento
        when 1 => rpc <= rpc + inc; -- salto relativo
        when others => rpc <= reg; -- salto absoluto
        end case;
     end if;
     end process;

     pc <= rpc;

end Structure;
--------------------------------------------------------------------------------

Bloque I_Control – Control.vhd

--------------------------------------------------------------------------------
-- SubModule Control
-- Created   12/09/2011 11:03:37 a.m.
--------------------------------------------------------------------------------
Library IEEE;
Use IEEE.Std_Logic_1164.all;
use ieee.std_logic_unsigned.all;
use ieee.std_logic_arith.all;

entity Control is
  port(
    opcode      : in  std_logic_vector(5 downto 0);
    flags       : in  std_logic_vector(2 downto 0);
    jump        : out std_logic_vector(1 downto 0);
    sel_rd_data : out std_logic_vector(1 downto 0);
    alu_op      : out std_logic_vector(3 downto 0);
    mem_rd      : out std_logic;
    mem_wr      : out std_logic;
    rd_wrl      : out std_logic;
    rd_wrh      : out std_logic
  );
end Control;
--------------------------------------------------------------------------------
architecture Structure of Control is
  type topcode is (
    OP_ADD, OP_SUB, OP_MUL, OP_AND, OP_OR, OP_NOT, OP_SL, OP_SR, -- Regs to Reg
    OP_MOV, -- Reg to Reg
    OP_JR, OP_J, OP_JE, OP_JNE, OP_JL, OP_JG, -- Jumps
    OP_LH, OP_LL, -- Load immediate
    OP_LW, OP_SW -- Load/Store Memory (Read/Write External Bus)
    );
  -- Tipo de salto
  type tjump is (J_NONE, J_JUMP_INC, J_JUMP_REG);
  -- Tipo de operacion posible en la ALU
  type taluop is (AO_ADD, AO_SUB, AO_MUL, AO_AND, AO_OR, AO_NOT, AO_SR, AO_SL, AO_BYPASS);
  -- Selectores de informacion que va al registro de destino
  type tselrd is (SR_IMM, SR_ALU, SR_MEM);
  -- Tipo de operacion de memoria / bus
  type tmemop is (MO_NONE, MO_READ, MO_WRITE);
  -- Tipos de escritura al register file
  type trfw is (RF_NONE, RF_BOTH, RF_LOW, RF_HIGH);

  signal iopcode   : topcode;
  signal ijump     : tjump;
  signal ialuop    : taluop;
  signal iselrd    : tselrd;
  signal imemop    : tmemop;
  signal irfw      : trfw;
  signal zeroflag  : std_logic;
  signal greatflag : std_logic;
  signal lessflag  : std_logic;
begin

  -- convierte el valor de entrada en valor simbolico
  iopcode   <= topcode'val(conv_integer(opcode));
  zeroflag  <= flags(0);
  greatflag <= flags(1);
  lessflag  <= flags(2);

  -- convierte los valores simbolicos en valores de salida
  jump        <= conv_std_logic_vector(tjump'pos(ijump), jump'length);
  alu_op      <= conv_std_logic_vector(taluop'pos(ialuop), alu_op'length);
  sel_rd_data <= conv_std_logic_vector(tselrd'pos(iselrd), sel_rd_data'length);
  mem_rd      <= '1' when imemop = MO_READ else '0';
  mem_wr <= '1' when imemop = MO_WRITE else '0';
  rd_wrl <= '1' when irfw = RF_BOTH or irfw = RF_LOW else '0';
  rd_wrh <= '1' when irfw = RF_BOTH or irfw = RF_HIGH else '0';

  process(iopcode, zeroflag, greatflag, lessflag)
  begin
    case iopcode is
      when OP_ADD =>
        ijump  <= J_NONE;
        iselrd <= SR_ALU;
        imemop <= MO_NONE;
        irfw   <= RF_BOTH;
        ialuop <= AO_ADD;
      when OP_SUB =>
        ijump  <= J_NONE;
        iselrd <= SR_ALU;
        imemop <= MO_NONE;
        irfw   <= RF_BOTH;
        ialuop <= AO_SUB;
      when OP_MUL =>
        ijump  <= J_NONE;
        iselrd <= SR_ALU;
        imemop <= MO_NONE;
        irfw   <= RF_BOTH;
        ialuop <= AO_MUL;
      when OP_AND =>
        ijump  <= J_NONE;
        iselrd <= SR_ALU;
        imemop <= MO_NONE;
        irfw   <= RF_BOTH;
        ialuop <= AO_AND;
      when OP_OR =>
        ijump  <= J_NONE;
        iselrd <= SR_ALU;
        imemop <= MO_NONE;
        irfw   <= RF_BOTH;
        ialuop <= AO_OR;
      when OP_NOT =>
        ijump  <= J_NONE;
        iselrd <= SR_ALU;
        imemop <= MO_NONE;
        irfw   <= RF_BOTH;
        ialuop <= AO_NOT;
      when OP_SL =>
        ijump  <= J_NONE;
        iselrd <= SR_ALU;
        imemop <= MO_NONE;
        irfw   <= RF_BOTH;
        ialuop <= AO_SL;
      when OP_SR =>
        ijump  <= J_NONE;
        iselrd <= SR_ALU;
        imemop <= MO_NONE;
        irfw   <= RF_BOTH;
        ialuop <= AO_SR;
      when OP_MOV =>
        ijump  <= J_NONE;
        iselrd <= SR_ALU;
        imemop <= MO_NONE;
        irfw   <= RF_BOTH;
        ialuop <= AO_BYPASS;

-- DC: significa Dont-Care (no importa)
      when OP_JR =>
        ijump  <= J_JUMP_REG;
        iselrd <= SR_ALU;       -- DC
        imemop <= MO_NONE;
        irfw   <= RF_NONE;
        ialuop <= AO_BYPASS;    -- DC

      when OP_J =>
        ijump  <= J_JUMP_INC;
        iselrd <= SR_ALU;       -- DC
        imemop <= MO_NONE;
        irfw   <= RF_NONE;
        ialuop <= AO_BYPASS;    -- DC

      when OP_JE =>
        if zeroflag = '1' then
          ijump <= J_JUMP_INC;
        else
          ijump <= J_NONE;
        end if;
        iselrd <= SR_ALU;       -- DC
        imemop <= MO_NONE;
        irfw   <= RF_NONE;
        ialuop <= AO_BYPASS;    -- DC
      when OP_JNE =>
        if zeroflag = '0' then
          ijump <= J_JUMP_INC;
        else
          ijump <= J_NONE;
        end if;
        iselrd <= SR_ALU;       -- DC
        imemop <= MO_NONE;
        irfw   <= RF_NONE;
        ialuop <= AO_BYPASS;    -- DC
      when OP_JL =>
        if lessflag = '1' then
          ijump <= J_JUMP_INC;
        else
          ijump <= J_NONE;
        end if;
        iselrd <= SR_ALU;       -- DC
        imemop <= MO_NONE;
        irfw   <= RF_NONE;
        ialuop <= AO_BYPASS;    -- DC
      when OP_JG =>
        if greatflag = '1' then
          ijump <= J_JUMP_INC;
        else
          ijump <= J_NONE;
        end if;
        iselrd <= SR_ALU;       -- DC
        imemop <= MO_NONE;
        irfw   <= RF_NONE;
        ialuop <= AO_BYPASS;    -- DC

      when OP_LH =>
        ijump  <= J_NONE;
        iselrd <= SR_IMM;
        imemop <= MO_NONE;
        irfw   <= RF_HIGH;
        ialuop <= AO_BYPASS;    -- DC
      when OP_LL =>
        ijump  <= J_NONE;
        iselrd <= SR_IMM;
        imemop <= MO_NONE;
        irfw   <= RF_LOW;
        ialuop <= AO_BYPASS;    -- DC

      when OP_LW =>
        ijump  <= J_NONE;
        iselrd <= SR_MEM;
        imemop <= MO_READ;
        irfw   <= RF_BOTH;
        ialuop <= AO_BYPASS;    -- DC
      when OP_SW =>
        ijump  <= J_NONE;
        iselrd <= SR_MEM;       -- DC
        imemop <= MO_WRITE;
        irfw   <= RF_NONE;
        ialuop <= AO_BYPASS;    -- DC

      when others =>
        ijump  <= J_NONE;
        iselrd <= SR_ALU;
        imemop <= MO_NONE;
        irfw   <= RF_NONE;
        ialuop <= AO_NOT;
    end case;
  end process;
end Structure;
--------------------------------------------------------------------------------

Descripción terminada…

La descripción del sistema en su forma funcional esta terminada. Con esto me refiero a que en este momento tenemos todo un diseño que incluye elementos básicos descritos con VHDL y cableados mediante diagramas de bloques (de manera que es mas sencilla su interpretación) para así formar un sistema completo. Recordemos los elementos que eran necesarios para describir un sistema en forma completa:

El paso siguiente  será obtener a partir de todo esto, una descripción totalmente en HDL (sin diagramas de bloques) de manera que nuestro sistema pueda ser simulado. Para lograr esto, le pediremos a Altium que convierta nuestros diagramas de bloques en archivos VHDL que representan el mismo cableado.

Noten que pudimos haber escrito todo el sistema usando VHDL, pero visualizar la estructura a partir del código VHDL no es para nada intuitivo, por el contrario la claridad de nuestro objetivo se pierde en el código, Es mucho mas intuitivo ingresar el diagrama al software y mantener estos diagramas por ello usamos una herramienta que nos facilitara el trabajo.

Implementación de la arquitectura Zetta – PASO 2


Ahora que tenemos un horizonte claro del sistema que estamos construyendo demos un paso hacia ABAJO (ya que estamos usando la metodología Top-Down).

Al bajar lo que estamos haciendo es adentrándonos en los detalles del sistema para llevar a cabo su implementación, esto es lo mismo que aumentar el nivel de detalle.

Usando en Altium podemos “bajar” usando la herramienta Create Sheet From Sheet Symbol. como se muestra en la imagen. Esto nos creará un nuevo diagrama de bloques con los puertos definidos en el bloque de manera que podemos ahi diseñar el interior de la caja negra (para que deje de serlo).

zetta1a

A continuación una imagen que refleja mi diseño en este nivel.

zetta2

En esta imagen se puede ver como he compuesto un sistema a partir de nuevos bloques funcionales que todavía no he definido internamente (por ahora son cajas negras)

Para que quede mas claro lo que hemos hecho aquí muestro como se relacionan los bloques.

zetta2a

Este nivel muestra claramente elementos nuevos que son producto de la creatividad del diseñador / ingeniero en donde se divide el problema general (procesador ZETTA) en subproblemas mas pequeños (Register File, ALU, Memoria de programa, Unidad de Control, Program counter). OJO: Note que las señales en magenta son señales que salen de la unidad de control, y están conectadas entre si cuando llevan el mismo nombre. Estas líneas no se extienden por todo el diagrama SOLO PARA PODER CONSERVAR EL DIAGRAMA ORDENADO Y UN POCO MAS CLARO DE ENTENDER.

Explicación del diseño

Bloque I_IRam = Memoria de Instrucciones

Esta es una memoria que contiene el programa que ejecutará el procesador que estamos construyendo. Aquí residen las instrucciones una a una en cada posición que esta almacena. Esta memoria saca a su salida data la instrucción en la posición que indica la señal address (que viene del contador de programa).

Bloque I_UnpackInst = Instruction Decoder / Desempacador de Instrucción

Este bloque toma la instrucción que sale de la memoria de instrucciones (I_IRam)  y separa los elementos que la constituyen para poder trabajarlos en los otros bloques. Los elementos que constituyen una instrucción estaban definidos cuando definimos la arquitectura en otro articulo.

Bloque I_PC = Program Counter / Contador de Programa

Registro que indica la dirección de la instrucción que esta siendo ejecutada.

Bloque I_RegFile = Register File / Banco de Registros

Es un conjunto de registros que el programador puede utilizar para realizar operaciones con los datos que desee procesar. Dependiendo de la instrucción que se ejecute se operan ciertos registros, por eso las entradas incluyen dos selectores de registro rs1 y rs2; y las salidas rs1_data y rs2_data son respectivamente la información de estos registros seleccionados.

Como también es posible guardar resultados de algunas instrucciones en un registro, este bloque también incluye unas entrada rd_wrh y rd_wrl que cuando están en 1 indican que se debe guardar la parte alta o baja respectivamente; una entrada rd que selecciona el registro de destino; y una entrada rd_data con los datos que serán guardados.

Bloque I_Alu = Arithmetic & Logic Unit / Unidad Aritmética y Lógica

Es un bloque que realiza las operaciones aritméticas o lógicas necesarias para poder cumplir con la ejecución de algunas instrucciones. Recibe la información de los registros seleccionados en el banco de registros y los opera (los suma, o los resta, o los multiplica, según sea el caso) dejando ver a su salida el resultado de la operación.

Bloque I_Mux = Multiplexor / Multiplexor

Este multiplexor permite seleccionar de donde se van a sacar los datos que se guardarán en el registro de destino de I_RegFile.

En el registro de destino se puede guardar un dato que es….

  • Proveniente del Bus o puerto de comunicación externo.
  • Resultado de una operación entre registros mediante la ALU
  • Un valor inmediato codificado en instrucciones LL y LH (instrucciones I-Type)

Bloque I_Control = Control Unit / Unidad de Control

La unidad de control aplica la lógica necesaria para configurar cada uno de los otros bloques según corresponda el caso para cumplir con la instrucción que esta siendo ejecutada. Es por eso que su principal entrada es opcode.

Implementación de la arquitectura Zetta – PASO 1


Para la implementación de nuestra arquitectura tengo una restricción: La FPGA de la que dispongo es un chip Cyclone II 2C20 de Altera que viene en la tarjeta de desarrollo DE1 ensamblada por Terasic sin embargo el procedimiento puede ser aplicado a otras tarjetas de desarrollo y chips FPGAs incluso (como las de Xilinx o Actel).

Adicionalmente aclaro que usaré el siguiente software:

Metodología de diseño TOP-DOWN

Como lo que estamos diseñando es un sistema, debemos definir claramente 3 cosas: Entradas, Salidas y Comportamiento. Las entradas y salidas las ingresaremos en forma grafica en Altium Designer.

Empezamos creando un proyecto de FPGA en Altium Designer (Menu New + Project + FPGA Project ) y añadimos un nuevo diagrama de bloques para empezar a ingresar nuestro diseño de sistema.

En la imagen puede verse como queda mi diseño de nivel superior del sistema general en un diagrama de bloques en Altium Designer.

zetta1

El sistema es un bloque (hasta ahora una caja negra) cuyas entradas son las señales clk, reset, y data_rd  y sus salidas son las señales address, data_wr, wr, y rd.

Con esto lo que estoy haciendo es dotando a mi sistema de un bus de comunicación con el mundo exterior, de esta manera el procesador podrá interactuar con otros dispositivos (memorias, LEDs, Registros, UARTs, etc) cuando el programa que este ejecutando use las instrucciones SW y LW.

Explicación de las señales

clk Señal de reloj cuyo flanco de subida es la base de tiempo del sistema.
reset Señal que lleva al sistema a un estado inicial conocido.
address[31..0] Dirección en el bus que se quiere acceder ya sea para escritura o lectura. Observe que la dirección posible es de 32 bits.
data_wr[31..0] El procesador pone en estas líneas el dato que quiere comunicar al dispositivo en la dirección seleccionada. Observe que dato es de 32 bits.
data_rd[31..0] El procesador recibe por estas líneas el dato que el dispositivo seleccionado por la dirección le quiere comunicar. Observe que dato es de 32 bits.
wr Se pone en 1 cuando el procesador quiere comunicar  algo (escribir en el bus).
rd Se pone en 1 cuando el procesador quiere recibir algo. (leer del bus)

Ya que se han explicado las señales, vamos a ver como el procesador puede usar el bus.

Transacción de Lectura (El procesador lee del bus)

  1. El procesador asigna una dirección del dispositivo que quiere leer.
  2. El procesador asigna rd = 1 y wr = 0   (si leer, no escribir)
  3. La señal clk realiza un cambio de 0 a 1 (flanco de subida)

La transacción se considera completa.

Transacción de Escritura (El procesador escribe del bus)

  1. El procesador asigna una dirección del dispositivo que quiere escribir.
  2. El procesador asigna el dato que quiere escribir en data_wr
  3. El procesador asigna rd = 0 y wr = 1   (no leer, si escribir)
  4. La señal clk realiza un cambio de 0 a 1(flanco de subida)

La transacción se considera completa.

Bus inactivo (sin transacción)

  1. El procesador asigna rd = 0 y wr = 0  (no leer, no escribir)
  2. La señal clk realiza un cambio de 0 a 1(flanco de subida)

El bus no realiza ninguna transacción ni de lectura ni de escritura en este ciclo de reloj.