VHDL: un register file de 16×64 bits, simulando una operación de suma en ensamblador

VHDL register file 16×64 simulando R3 <- R1 + R2

Hoy traigo otro experimento algo más largo de lo normal, otro code-kata para la serie de posts sobre VHDL. Este code-kata es sobre lo que se llama un register file, que simplemente es un circuito compuesto por registros sencillos. En este caso son registros de 64 bits con precarga, y unos decodificadores para apuntar a uno y otro. En concreto, se trata de un register file de 16 registros, y dos decodificadores de 4×16 con entrada de habilitación, para elegir uno de los 16 registros al escribir o leer los datos.

Los register file son conjuntos de registros que se utilizan comunmente en los microprocesadores para varias tareas. Por ejemplo, se pueden usar como almacén de datos, de direcciones, contadores, acumuladores, etc.. En esta construcción del circuito, se simula el uso como almacén de datos, para simular el uso en la operación de ensamblador siguiente:

R3 <- R1 + R2

En este caso, tenemos direccionamiento a los registros con 4 bits, que son las 16 posibilidades. Con lo que R1 está en la dirección 0001, R2 en 0010, y R3 en 0011. En la simulación simplemente se hacen escrituras y lecturas sobre estos registros para comprobar visualmente si funciona. Quedan en el aire todos los demás circuitos, como la ALU para sumar, buses, el autómata que orquesta todo el funcionamiento, etc..

Vamos por partes..

Primero el código de un registro simple con pre-carga de 64 bits

library IEEE;
use IEEE.std_logic_1164.all;

entity loadRegister64 is
    port (
        valueOut : out std_logic_vector (63 downto 0);
        valueIn : in std_logic_vector (63 downto 0);
        reset, clock, load : in std_logic
    );
end entity;

architecture arch_loadRegister64 of loadRegister64 is

    signal auxValueOut : std_logic_vector (63 downto 0);

begin

    valueOut <= auxValueOut;

    identifier : process (clock)
    begin
        if rising_edge(clock) then
            if reset = '1' then
                auxValueOut <= x"0000000000000000";
            elsif load = '1' then
                auxValueOut <= valueIn;
            end if;
        end if;
    end process;

end architecture;

Ahora el banco de pruebas del registro simple

library IEEE;
use IEEE.std_logic_1164.all;

entity loadRegister64_tb is
end entity;

architecture arch_loadRegister64_tb of loadRegister64_tb is

    component loadRegister64 is
        port (
            valueOut : out std_logic_vector (63 downto 0);
            valueIn : in std_logic_vector (63 downto 0);
            reset, clock, load : in std_logic
        );
    end component;

    signal testReset, testClock, testLoad : std_logic := '0';
    signal testValueOut, testValueIn : std_logic_vector (63 downto 0);
    signal tics : integer := 0;

begin

    testing_unit: loadRegister64 port map (
        valueOut => testValueOut,
        valueIn => testValueIn,
        reset => testReset,
        clock => testClock,
        load => testLoad
    );

    generate_100Mhzs_clock : process
    begin

        report "Tic tac..";
        testClock <= not testClock;
        wait for 5 ns; -- Tiempo de espera en un flanco de reloj.

        if testClock = '1' then tics <= tics + 1; end if;
        if tics >= 10 then wait; end if;

    end process;

    generate_signals : process
    begin

        testReset <= '1'; testValueIn <= x"00000000FFFFFFFF";

        wait for 5 ns;
        testReset <= '0'; wait for 10 ns;
        testLoad <= '1'; wait for 10 ns;

        testValueIn <= x"000000000000000A"; wait for 10 ns;
        testValueIn <= x"000000000000000B"; wait for 10 ns;
        testValueIn <= x"000000000000000C"; wait for 10 ns;

        testLoad <= '0'; wait for 10 ns;

        testReset <= '1'; wait for 10 ns;
        testReset <= '0'; wait for 10 ns;

        wait;

    end process;

end architecture;

El código del decodificador de 4×16 con señal de habilitación

library IEEE;
use IEEE.std_logic_1164.all;

entity decoder4x16 is
    port (
        -- Tiene 4 pines de entrada y 16 de salida.
        y : out std_logic_vector (15 downto 0);
        x : in std_logic_vector (3 downto 0);
        enable : in std_logic
    );
end entity;

architecture arch_decoder4x16 of decoder4x16 is
begin

    identifier : process(enable)
    begin
        if enable = '1' then
            if x = "0000" then
                y <= "0000000000000001";
            elsif x = "0001" then
                y <= "0000000000000010";
            elsif x = "0010" then
                y <= "0000000000000100";
            elsif x = "0011" then
                y <= "0000000000001000";
            elsif x = "0100" then
                y <= "0000000000010000";
            elsif x = "0101" then
                y <= "0000000000100000";
            elsif x = "0110" then
                y <= "0000000001000000";
            elsif x = "0111" then
                y <= "0000000010000000";
            elsif x = "1000" then
                y <= "0000000100000000";
            elsif x = "1001" then
                y <= "0000001000000000";
            elsif x = "1010" then
                y <= "0000010000000000";
            elsif x = "1011" then
                y <= "0000100000000000";
            elsif x = "1100" then
                y <= "0001000000000000";
            elsif x = "1101" then
                y <= "0010000000000000";
            elsif x = "1110" then
                y <= "0100000000000000";
            elsif x = "1111" then
                y <= "1000000000000000";
            else
                -- Si no es una señal bien definida..
                y <= "UUUUUUUUUUUUUUUU";
            end if;
        else
            y <= "0000000000000000";
        end if;

    end process;

end architecture;

El banco de pruebas del decodificador

library IEEE;
use IEEE.std_logic_1164.all;

entity decoder4x16_tb is
end entity;

architecture arch_decoder4x16_tb of decoder4x16_tb is

    -- Declaramos el componente que vamos a cablear y probar con este banco
    -- de pruebas.
    component decoder4x16 is
        port (
            y : out std_logic_vector (15 downto 0);
            x : in std_logic_vector (3 downto 0);
            enable : in std_logic
        );
    end component;

    -- Señales auxiliares para cablear y probar entradas y salidas.
    signal outVect : std_logic_vector (16 downto 1);
    signal inVect : std_logic_vector (4 downto 1);
    signal testEnable : std_logic;

begin

    -- El port map, cableando el componente de arriba con las señales de
    -- éste código. Así podemos probar..
    unit_under_test: component decoder4x16 port map (
        y => outVect,
        x => inVect,
        enable => testEnable
    );

    -- Proceso final de pruebas. No hago comprobaciones,
    -- sólo para visualizar señales y comprobar así funcionamiento.
    decode_signals_process : process
    begin

        testEnable <= '1'; inVect <= "0000"; wait for 10 ns;
        testEnable <= '0'; inVect <= "0001"; wait for 10 ns;
        testEnable <= '1'; inVect <= "0010"; wait for 10 ns;
        testEnable <= '0'; inVect <= "0011"; wait for 10 ns;
        testEnable <= '1'; inVect <= "0100"; wait for 10 ns;
        testEnable <= '0'; inVect <= "0101"; wait for 10 ns;
        testEnable <= '1'; inVect <= "0110"; wait for 10 ns;
        testEnable <= '0'; inVect <= "0111"; wait for 10 ns;
        testEnable <= '1'; inVect <= "1000"; wait for 10 ns;
        testEnable <= '0'; inVect <= "1001"; wait for 10 ns;
        testEnable <= '1'; inVect <= "1010"; wait for 10 ns;
        testEnable <= '0'; inVect <= "1011"; wait for 10 ns;
        testEnable <= '1'; inVect <= "1100"; wait for 10 ns;
        testEnable <= '0'; inVect <= "1101"; wait for 10 ns;
        testEnable <= '1'; inVect <= "1110"; wait for 10 ns;
        testEnable <= '0'; inVect <= "1111"; wait for 10 ns;
        wait;
    end process;

end architecture;

Finalmente, el código del register file de 16 registros de 64 bits cada uno

library IEEE;
use IEEE.std_logic_1164.all;

entity registerFile16x64 is
    port (
        readData : out std_logic_vector (63 downto 0);
        writeData : in std_logic_vector (63 downto 0);

        writeAddress : in std_logic_vector (3 downto 0);
        writeEnable : in std_logic;

        readAddress : in std_logic_vector (3 downto 0);
        readEnable : in std_logic;

        mainReset : in std_logic;
        mainClock : in std_logic
    );
end entity;

architecture arch_registerFile16x64 of registerFile16x64 is

    component loadRegister64 is
        port (
            valueOut : out std_logic_vector (63 downto 0);
            valueIn : in std_logic_vector (63 downto 0);
            reset, clock, load : in std_logic
        );
    end component;

    component decoder4x16 is
        port (
            -- Tiene 4 pines de entrada y 16 de salida.
            y : out std_logic_vector (15 downto 0);
            x : in std_logic_vector (3 downto 0);
            enable : in std_logic
        );
    end component;

    signal enableRegisterToWrite : std_logic_vector (15 downto 0);
    signal enableRegisterToRead : std_logic_vector (15 downto 0);

begin

    -- Decodificador para la escritura en registro que corresponda.
    decode_write_address : decoder4x16 port map (
        y => enableRegisterToWrite,
        x => writeAddress,
        enable => writeEnable
    );
    -- Decodificador para leer el registro.
    decode_read_address : decoder4x16 port map (
        y => enableRegisterToRead,
        x => readAddress,
        enable => readEnable
    );

    -- Los 16 registros, del R0 al R15.
    register_R0 : loadRegister64 port map (
        valueOut => readData,
        valueIn => writeData,
        reset => mainReset,
        clock => mainClock,
        load => enableRegisterToWrite(0)
    );
    register_R1 : loadRegister64 port map (
        valueOut => readData,
        valueIn => writeData,
        reset => mainReset,
        clock => mainClock,
        load => enableRegisterToWrite(1)
    );
    register_R2 : loadRegister64 port map (
        valueOut => readData,
        valueIn => writeData,
        reset => mainReset,
        clock => mainClock,
        load => enableRegisterToWrite(2)
    );
    register_R3 : loadRegister64 port map (
        valueOut => readData,
        valueIn => writeData,
        reset => mainReset,
        clock => mainClock,
        load => enableRegisterToWrite(3)
    );
    register_R4 : loadRegister64 port map (
        valueOut => readData,
        valueIn => writeData,
        reset => mainReset,
        clock => mainClock,
        load => enableRegisterToWrite(4)
    );
    register_R5 : loadRegister64 port map (
        valueOut => readData,
        valueIn => writeData,
        reset => mainReset,
        clock => mainClock,
        load => enableRegisterToWrite(5)
    );
    register_R6 : loadRegister64 port map (
        valueOut => readData,
        valueIn => writeData,
        reset => mainReset,
        clock => mainClock,
        load => enableRegisterToWrite(6)
    );
    register_R7 : loadRegister64 port map (
        valueOut => readData,
        valueIn => writeData,
        reset => mainReset,
        clock => mainClock,
        load => enableRegisterToWrite(7)
    );
    register_R8 : loadRegister64 port map (
        valueOut => readData,
        valueIn => writeData,
        reset => mainReset,
        clock => mainClock,
        load => enableRegisterToWrite(8)
    );
    register_R9 : loadRegister64 port map (
        valueOut => readData,
        valueIn => writeData,
        reset => mainReset,
        clock => mainClock,
        load => enableRegisterToWrite(9)
    );
    register_R10 : loadRegister64 port map (
        valueOut => readData,
        valueIn => writeData,
        reset => mainReset,
        clock => mainClock,
        load => enableRegisterToWrite(10)
    );
    register_R11 : loadRegister64 port map (
        valueOut => readData,
        valueIn => writeData,
        reset => mainReset,
        clock => mainClock,
        load => enableRegisterToWrite(11)
    );
    register_R12 : loadRegister64 port map (
        valueOut => readData,
        valueIn => writeData,
        reset => mainReset,
        clock => mainClock,
        load => enableRegisterToWrite(12)
    );
    register_R13 : loadRegister64 port map (
        valueOut => readData,
        valueIn => writeData,
        reset => mainReset,
        clock => mainClock,
        load => enableRegisterToWrite(13)
    );
    register_R14 : loadRegister64 port map (
        valueOut => readData,
        valueIn => writeData,
        reset => mainReset,
        clock => mainClock,
        load => enableRegisterToWrite(14)
    );
    register_R15 : loadRegister64 port map (
        valueOut => readData,
        valueIn => writeData,
        reset => mainReset,
        clock => mainClock,
        load => enableRegisterToWrite(15)
    );

end architecture;

El banco de pruebas del register file, la simulación

Aquí es donde finalmente sólo se prueba si funciona bien haciendo un experimento de funcionamiento real con la operación en ensamblador de suma. Para esto se inicializa todo, luego se cargan unos valores en R1 y R2 de pruebas, se leen, se escribe un resultado simulando que la ALU devolvió el resultado, y escribe en R3 el resultado:

library IEEE;
use IEEE.std_logic_1164.all;

entity registerFile16x64_tb is
end entity;

architecture arch_registerFile16x64_tb of registerFile16x64_tb is

    component registerFile16x64 is
        port (
            readData : out std_logic_vector (63 downto 0);
            writeData : in std_logic_vector (63 downto 0);

            writeAddress : in std_logic_vector (3 downto 0);
            writeEnable : in std_logic;

            readAddress : in std_logic_vector (3 downto 0);
            readEnable : in std_logic;

            mainReset : in std_logic;
            mainClock : in std_logic
        );
    end component;

    signal testReadData, testWriteData : std_logic_vector (63 downto 0) := x"0000000000000000";
    signal testWriteAddress, testReadAddress : std_logic_vector (3 downto 0) := x"0";
    signal testWriteEnable, testReadEnable : std_logic := '0';
    signal testReset, testClock : std_logic := '0';

    signal tics : integer := 0;

begin

    testing_unit : registerFile16x64 port map (
        readData => testReadData,
        writeData => testWriteData,
        writeAddress => testWriteAddress,
        writeEnable => testWriteEnable,
        readAddress => testReadAddress,
        readEnable => testReadEnable,
        mainReset => testReset,
        mainClock => testClock
    );

    generate_100Mhzs_clock : process
    begin

        -- Haciendo el tic tac del reloj..
        report "Tic tac..";
        testClock <= not testClock;
        wait for 5 ns;

        -- Contamos tics para hacer sólo unos cuantos.
        if testClock = '1' then tics <= tics + 1; end if;
        if tics >= 20 then wait; end if;

    end process;

    generate_signals : process
    begin

        wait for 5 ns;

        testReset <= '1'; wait for 10 ns; testReset <= '0';

        -- Escribimos algo en R1 y R2..

        testWriteEnable <= '1';
        testWriteData <= x"000000000000AAAA";
        testWriteAddress <= "0001";
        wait for 10 ns;
        testWriteEnable <= '0';
        testWriteData <= x"0000000000000000";
        wait for 10 ns;

        testWriteEnable <= '1';
        testWriteData <= x"000000000000BBBB";
        testWriteAddress <= "0010";
        wait for 10 ns;
        testWriteEnable <= '0';
        testWriteData <= x"0000000000000000";
        wait for 10 ns;

        -- Simulando cargas y lecturas en registros para la
        -- operación en ensamblador:
        -- R3 <- R1 + R2

        -- Lectura de R1
        testReadEnable <= '1'; testReadAddress <= "0001"; wait for 10 ns;
        testReadEnable <= '0'; wait for 10 ns;

        -- Lectura de R2
        testReadEnable <= '1'; testReadAddress <= "0010"; wait for 10 ns;
        testReadEnable <= '0'; wait for 10 ns;

        -- Esperamos resultado de la operación
        wait for 30 ns;

        -- Escribimos un resultado en R3
        testWriteEnable <= '1';
        testWriteData <= x"0000000000016665";
        testWriteAddress <= "0011"; wait for 10 ns;
        testWriteEnable <= '0'; wait for 10 ns;

        wait;
    end process;

end architecture;

Si todo va bien, al simular el banco de pruebas del register file se debería de ver una imagen como la del principio.

Compartir..

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *