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.