EJERCICIOS DE SISTEMAS ELECTRÓNICOS DIGITALES: HOJA 1 2o CURSO DE INGENIERÍA TÉCNICA INDUSTRIAL. ESPECIALIDAD EN ELECTRÓNICA INDUSTRIAL LENGUAJES DE ALTO NIVEL 1.- Determina la funcionalidad del siguiente código VHDL. LIBRARY ieee; USE ieee.std_logic_1164.all; ENTITY mux IS PORT ( a,b,c,d: IN STD_LOGIC_VECTOR (2 DOWNTO 0); seleccio: IN INTEGER RANGE 0 TO 3; enable: IN STD_LOGIC; e: OUT STD_LOGIC_VECTOR (2 DOWNTO 0)); END mux; ARCHITECTURE comportament OF mux IS BEGIN PROCESS (a,b,seleccio,enable) BEGIN IF enable=’1’ THEN CASE seleccio IS WHEN 0 => e <= a; WHEN 1=> e <= b; WHEN 2=> e <= c; WHEN 3=> e <= d; END CASE; ELSE e <= "ZZZ"; END IF; END PROCESS; END comportament; 2.- Describe el anterior código funcional en forma concurrente o RTL. 3.- Implementa en VHDL una unidad aritmética de forma que sobre dos palabras ‘a’ y ‘b’ de 4 bits realice las funciones suma, resta, incremento de ‘a’ en una unidad, y decremento de ‘a’ una unidad en función de una variable de selección. Una vez implementada haz la simulación con MAX+PLUS II 4.- Implementa en VHDL una unidad lógica de forma que sobre dos palabras ‘a’ y ‘b’ de 4 bits realice las funciones AND, OR, XOR y NOT ‘a’ en función de una variable de selección. Una vez implementada haz la simulación con MAX+PLUS II 5.- Implementa una ALU de cuatro bits que realice las ocho funciones anteriores (a+b, a-b, a+1, a-1, a AND b, a OR b, a XOR b, NOT a) en función de una variable de selección. Realiza su simulación con el MAX+PLUSII. 6.- Indica la funcionalidad del siguiente código VHDL. LIBRARY ieee; USE ieee.std_logic_1164.all; ENTITY entity IS PORT ( A : IN STD_LOGIC; B : IN STD_LOGIC; C : OUT STD_LOGIC ); END entity; ARCHITECTURE arch OF entity IS BEGIN PROCESS (A, B) BEGIN IF A = '0' THEN C <= 'Z'; ELSE C <= B; END IF; END PROCESS; END arch; 7.- Indica la funcionalidad del siguiente código VHDL. LIBRARY ieee; USE ieee.std_logic_1164.all; ENTITY ent IS PORT ( A : IN INTEGER RANGE 0 TO N; CLK : IN STD_LOGIC; CLR : IN STD_LOGIC; EN : IN STD_LOGIC; LD : IN STD_LOGIC; SORTIDA : OUT INTEGER RANGE 0 TO N ); END ent; ARCHITECTURE arch OF ent IS SIGNAL S : INTEGER RANGE 0 TO N; BEGIN PROCESS (CLK, CLR) BEGIN IF CLR = '0' THEN S <= 0; ELSIF (CLK’EVENT AND CLK = ’1’) THEN IF LD = ’1’ THEN S <= A; ELSE IF EN = ’1’ THEN S <= S + 1; ELSE S <= S; END IF; END IF; END IF; END PROCESS; SORTIDA <= S; END arch; 8.- Analiza el diseño en VHDL de un procesador sencillo de 16 bits. El procesador es capaz de ejecutar un número reducido de instrucciones que serán almacenadas en una RAM (incorporada en una librería de altera). La descripción del circuito es funcional y cada punto esta comentado detalladamente. El alumno ha de ser capaz de entender cada línea de código del diseño e implementarlo en el programa MAX+PLUSII de ALTERA. Así mismo ha de ser capaz de utilizar dicho código para incluirlo en un diseño que realice las especificaciones que se indican: a) El primer paso a realizar es la comprensión de cada línea de código que describe el procesador (para eso consulta las anotaciones de VHDL impartidas en clase). b) Una vez entendido el funcionamiento realiza un esquema de la arquitectura a nivel de bloques y describe las instrucciones que hay pre-programadas así como su correspondiente código de instrucción. c) Implementa el programa de la RAM que hay indicado en el fichero .mif y compila el diseño con el programa MAX+PLUSII. Realiza una simulación del funcionamiento del procesador y calcula la frecuencia máxima a la cual puede operar. Finalmente calcula el número de puertas lógicas que son usadas en la implementación del diseño a partir de los resultados de la compilación (que son guardados en el fichero .rpt). Código del Microprocesador sencillo en VHDL A continuación detallamos el código VHDL del microprocesador. -- Microprocesador sencillo --Esta linea habilita la libreria IEEE, de la cual usamos los "package" del std_logic_1164, --IEEE.STD_LOGIC_ARITH y IEEE.STD_LOGIC_UNSIGNED, que me permiten operar con variables del --tipo standard_logic LIBRARY IEEE; USE IEEE.STD_LOGIC_1164.ALL; USE IEEE.STD_LOGIC_ARITH.ALL; USE IEEE.STD_LOGIC_UNSIGNED.ALL; -- Abrimos la libreria lpm, en la que tenemos distintos componentes de libreria (ver ayuda -- para conocer los distintos componentes que disponemos LIBRARY lpm; USE lpm.lpm_components.ALL; --Definición de las entradas y las salidas del microprocesador ENTITY microsencill IS PORT(clock, reset : IN STD_LOGIC; register_input : IN STD_LOGIC_VECTOR(7 DOWNTO 0); register_output : OUT STD_LOGIC_VECTOR(7 DOWNTO 0); program_counter_out : OUT STD_LOGIC_VECTOR(7 DOWNTO 0); register_A_out, memory_data_register_out : OUT STD_LOGIC_VECTOR(15 DOWNTO 0)); END microsencill; --Arquitectura interna del mircoprocesador, consta de una unidad de control con --descrita en modo comportamental y de una memoria RAM de la libreria lpm ARCHITECTURE a OF microsencill IS -- Definimos un tipo de variable STATE_TYPE que me indica los posibles estados en los que -- puede estar el microprocesador TYPE STATE_TYPE IS (reset_pc, fetch, decode, execute_add, execute_load, execute_store, execute_store3, execute_store2, execute_jump, execute_jneg, execute_sub, execute_xor, execute_or, execute_and, execute_jpos, execute_jzero, execute_addi, execute_in, execute_out); --Definición de señales, la variable state me indica el estado del procesador SIGNAL state : STATE_TYPE; SIGNAL instruction_register, memory_data_register, register_A : STD_LOGIC_VECTOR(15 DOWNTO 0); SIGNAL program_counter, memory_address_register : STD_LOGIC_VECTOR(7 DOWNTO 0); -- Si memory_write=1 entonces escribiremos en memoria, si memory_write=0 lo que hacemos es leer SIGNAL memory_write : STD_LOGIC; BEGIN -- Usamos la memoria lpm_ram_dq (ver ayuda para descripción de esta memória) memory: lpm_ram_dq GENERIC MAP (lpm_widthad => 8, -- aqui definimos la longitud del bus de direcciones (8bits) lpm_outdata => "UNREGISTERED", lpm_indata => "REGISTERED", lpm_address_control => "UNREGISTERED", -- Definimos si el acceso, los datos de entrada y la salida son síncronos o asíncronos, -- como la salida la definimos como asíncrona no hace falta definir clock de salida (clock_out), -- (ver instrucciones de la ram en la ayuda) -- El fichero "program6.mif" me introduce los datos del programa que deseo me haga el procesador lpm_file => "program6.mif", lpm_width => 16) -- lpm_width me indica la anchura del bus de datos (16 bits) -- Definición de interconexión entre la memoria y las señales (que me definen la conexión con --la unidad de control PORT MAP (data => Register_A, address => memory_address_register, we => memory_write, inclock => clock, q => memory_data_register); -- Relación entre señales internas y la salida del micro (registre_A_out me muestra el contenido -- del acumulador, memory_data_register_out me muestra la salida de la memoria, program_counter_out -- indica el punto del programa en el que me encuentro program_counter_out <= program_counter; register_A_out <= register_A; memory_data_register_out <= memory_data_register; --A continuación detallo el comportamiento de la unidad de control PROCESS (CLOCK, RESET) BEGIN IF reset = '1' THEN state <= reset_pc; -- La unidad de control me ejecuta una instruccion a partir de cada flanco de subida. ELSIF clock'EVENT AND clock = '1' THEN CASE state IS -- dependiendo del estado hacemos una u otra operación, reset_pc me pone los registros a 0 WHEN reset_pc => program_counter <= "00000000"; memory_address_register <= "00000000"; register_A <= "0000000000000000"; memory_write <= '0'; state <= fetch; -- El estado "fetch" es el estado de búsqueda de instrucciones, carga la salida de la memoria --al registro de instrucciones e incrementa el contador de programa en 1 WHEN fetch => instruction_register <= memory_data_register; program_counter <= program_counter + 1; memory_write <= '0'; state <= decode; -- Segidamente se pasaria al estado de decodificación (fijaros que esto pasaría en el siguiente -- ciclo de reloj. WHEN decode => --Cargamos el contenido del registro de instrucciones (últimos 8 bits) -- en el registro de direcciones de la memoria (por si se ha de hacer algún acceso -- al decodificar luego una instrucción memory_address_register <= instruction_register(7 Downto 0); CASE instruction_register(15 downto 8) IS WHEN "00000000" => -Cuando los 8 primeros bits del registro de state <= execute_add; -instrucciones son 0 ejecutamos la función add WHEN "00000001" => -(suma acumulador a contenido memoria) state <= execute_store; -- Si el "upcode" es 01 entonces función store WHEN "00000010" => -(carga contenido del acc. en la memoria) state <= execute_load; -- Si el "upcode" es 02 ejecuta función load WHEN "00000011" => -Si el "upcode" es 03 ejecuta función jump state <= execute_jump; WHEN "00000100" => -Si el "upcode" es 04 ejecuta función jneg state <= execute_jneg; WHEN "00000101" => -Si el "upcode" es 05 ejecuta función sub state <= execute_sub; WHEN "00000110" => -Si el "upcode" es 06 ejecuta función xor state <= execute_xor; WHEN "00000111" => -Si el "upcode" es 07 ejecuta función or state <= execute_or; WHEN "00001000" => -Si el "upcode" es 08 ejecuta función and state <= execute_and; WHEN "00001001" => -Si el "upcode" es 09 ejecuta función jpos state <= execute_jpos; WHEN "00001010" => -Si el "upcode" es 0A ejecuta función jzero state <= execute_jzero; WHEN "00001011" => -Si el "upcode" es 0B ejecuta función addi state <= execute_addi; WHEN "00001100" => -Si el "upcode" es 0C ejecuta función in state <= execute_in; WHEN "00001101" => -Si el "upcode" es 0D ejecuta función out state <= execute_out; WHEN OTHERS => -Cualquier otro código pasa a búsqueda state <= fetch; END CASE; -- Ejecuta la función add WHEN execute_add => register_a <= register_a + memory_data_register; memory_address_register <= program_counter; state <= fetch; -- Ejecuta la función store (en tres ciclos para escribir en memória) WHEN execute_store => -- write register_A to memory memory_write <= '1'; state <= execute_store2; -- Este estado se asegura que la dirección de la memoria es válido hasta que acaba el ciclo -- de escritura WHEN execute_store2 => memory_write <= '0'; state <= execute_store3; WHEN execute_store3 => memory_address_register <= program_counter; state <= fetch; -- Ejecuta la instrucción load en donde se carga el contenido de la memoria -- en el acumulador. WHEN execute_load => register_a <= memory_data_register; memory_address_register <= program_counter; state <= fetch; -- Ejecuta la instrucción JUMP incrementando el contador de programa -- hasta donde diga el registro de instrucciones WHEN execute_jump => memory_address_register <= instruction_register(7 Downto 0); program_counter <= instruction_register(7 Downto 0); state <= fetch; -- Ejecuta la instrucción JNEG (salta si el acumulador es negativo) WHEN execute_jneg => IF register_A(15) = '1' THEN memory_address_register <= instruction_register(7 Downto 0); program_counter <= instruction_register(7 Downto 0); ELSE memory_address_register <= program_counter; END IF; state <= fetch; -- Ejecuta la función sub (resta del registro el contenido de la memoria WHEN execute_sub => register_a <= register_a - memory_data_register; memory_address_register <= program_counter; state <= fetch; -- Ejecuta la función XOR WHEN execute_xor => register_a <= register_a XOR memory_data_register; memory_address_register <= program_counter; state <= fetch; -- Ejecuta la función OR WHEN execute_or => register_a <= register_a OR memory_data_register; memory_address_register <= program_counter; state <= fetch; -- Ejecuta la función AND WHEN execute_and => register_a <= register_a AND memory_data_register; memory_address_register <= program_counter; state <= fetch; -- Ejecuta la función jpos (salta si el acumulador es positivo) WHEN execute_jpos => IF register_A(15) = '0' THEN IF NOT (register_A = "0000000000000000") THEN memory_address_register <= instruction_register(7 DOWNTO 0); program_counter <= instruction_register(7 DOWNTO 0); ELSE memory_address_register <= program_counter; END IF; ELSE memory_address_register <= program_counter; END IF; state <= fetch; -- Ejecuta la función jzero (salta si el acumulador es cero) WHEN execute_jzero => IF register_A = "0000000000000000" THEN memory_address_register <= instruction_register(7 Downto 0); program_counter <= instruction_register(7 Downto 0); ELSE memory_address_register <= program_counter; END IF; state <= fetch; -- Ejecuta la función addi, suma al acumulador el número inmediato -- del registro de instrucciones WHEN execute_addi => IF(instruction_register(7) = '1') THEN register_a <= register_a+ ("11111111" & instruction_register(7 DOWNTO 0)); ELSE register_a <= register_a+ ("00000000" & instruction_register(7 DOWNTO 0)); END IF; memory_address_register <= program_counter; state <= fetch; -- Ejecuta la función in (carga en el acumulador el valor que tenemos en la entrada -- exterior WHEN execute_in => register_a(7 DOWNTO 0) <= register_input; memory_address_register <= program_counter; state <= fetch; -- Saca en el pin de salida el contenido del acumulador (últimos 8 bits) WHEN execute_out => register_output <= register_a(7 DOWNTO 0); memory_address_register <= program_counter; state <= fetch; WHEN OTHERS => memory_address_register <= program_counter; state <= fetch; END CASE; END IF; END PROCESS; END a; Fichero de configuración de la memoria (.mif) A continuación mostramos un ejemplo de fichero de configuración de la memoria que sirve para programar la memoria RAM del microprocesador. -- program6.mif Depth = 256; Width = 16; Address_radix = HEX; Data_radix = HEX; Content Begin [00..FF]: 00000000; -- nop 00: 0C02; -- Entrada de un número 01: 0B01; -- Añade 1 a la entrada 02: 0D02; -- Pasa el resultado a la salida 03: 0300; -- Vuelve a empezar End;