diseÑo, implementaciÓn y test de un core risc-v
TRANSCRIPT
TRABAJO DE FINAL DE GRADO
Grado en Ingeniería Electrónica Industrial y Automática
DISEÑO, IMPLEMENTACIÓN Y TEST DE UN CORE RISC-V
Memoria y Anexos
Autor/a: Oscar Loscos Enríquez Director/a: Jordi Cosp Vilella Convocatoria: Enero 2021
Diseño, implementación y test de un core RISC-V
i
Resumen
En este trabajo de final de grado se diseña e implementa, usando el lenguaje VHDL, el núcleo de un
microcontrolador con el conjunto de instrucciones RISC-V, concretamente el conjunto base RV32I. El
objetivo es crear un microprocesador capacitado para el mundo laboral, pues el conjunto de
instrucciones es comercial y de uso libre y gratuito y la placa usada para la implementación del diseño
tiene unas características decentes, y con un nivel de complejidad apto para poder trabajarlo en
universidades u otros estudios relacionados con la electrónica digital.
Para ello, se ha llevado a cabo una búsqueda de información necesario sobre el lenguaje, conjunto de
instrucciones y la teoría que se trabaja, se estudia el listado de instrucciones que se deben ejecutar, se
diseña el datapath, que es el esquema principal del diseño y, por último, la unidad de control la cual se
implementa junto al datapath para completar el funcionamiento del procesador.
Una vez el diseño esta completado, se verifica que su funcionamiento sea correcto y posteriormente
se sintetiza y se implementa, de manera que se puede asegurar que es físicamente viable y apto para
la placa seleccionada.
Memoria
ii
Resum
A aquest treball de fi de grau es dissenya i implementa, fent ús del llenguatge VHDL, el nucli d’un
microcontrolador amb el conjunt d’instruccions RISC-V, concretament el conjunt base RV32I. L’objectiu
es crear un microprocessador capacitat per al món laboral, doncs el conjunt d’instruccions es comercial
i d’ús lliure i gratuït i la placa utilitzada per a la implementació del disseny té unes característiques
decents, i amb un nivell de complexitat apte per a poder treballar-ho en universitats i altres estudis
relacionats amb la electrònica digital.
Per a això, s’ha dut a terme una recerca d’informació necessària sobre el llenguatge, conjunt
d’instruccions i la teoria que es treballa, s’estudia el llistat d’instruccions que s’han d’executar, es
dissenya el datapath, que es l’esquema principal del disseny i, per últim, la unitat de control la qual
s’implementa juntament al datapath per a completar el funcionament del processador.
Una vegada el disseny es complert, es verifica que el seu funcionament sigui correcte i posteriorment
es sintetitza i s’implementa, de manera que es pugui assegurar que es físicament viable i apte per a la
placa seleccionada.
Diseño, implementación y test de un core RISC-V
iii
Abstract
In this bachelor’s thesis the core of a microcontroller is designed and implemented, using the language
VHDL, with the instruction set RISC-V, specifically the base set RV32I. The objective is to create a
microprocessor capable for working with it, since the instruction set is commercial and of open and
free use and the board used for the implementation of the design have a decent characteristic, and
with a suitable complexity level for working it in university or another digital electronic related studies.
In order to achieve it, it’s been conducted a necessary information research about the language, the
instruction set and the theory that it’s being working with, it’s studied the list of instructions that are
going to be executed, the datapath is designed, that is the principal scheme of the design, and, lastly,
the control unit is implemented next to the datapath for completing the processor operating.
Once the design is completed, the correct operation is verified and afterwards is synthetized and
implemented, to secure that is physically feasible and suitable for the selected board.
Diseño, implementación y test de un core RISC-V
v
Índice de contenido
RESUMEN ___________________________________________________________ I
RESUM _____________________________________________________________ II
ABSTRACT __________________________________________________________ III
1. INTRODUCCIÓN _________________________________________________ 1
1.1. Motivación ............................................................................................................... 1
1.2. Objetivos del trabajo ................................................................................................ 1
1.3. Alcance del trabajo .................................................................................................. 2
2. MICROCONTROLADORES __________________________________________ 3
2.1. Computadores.......................................................................................................... 3
2.1.1 Arquitectura de Von Neuman y arquitectura de Harvard ..................................... 3
2.2. Microprocesadores .................................................................................................. 5
2.2.1 Ciclo de trabajo ....................................................................................................... 5
2.2.2 Unidad de ejecución y unidad de control .............................................................. 5
2.2.3 Conjunto de instrucciones, ISA ............................................................................... 6
2.2.4 Microarquitectura .................................................................................................. 6
2.3. Microcontroladores ................................................................................................. 7
2.4. RISC-V ....................................................................................................................... 8
2.4.1 Código abierto ........................................................................................................ 9
2.4.2 Convención de nombres de conjuntos base y extensiones ................................... 9
2.5. Estado del arte ....................................................................................................... 10
2.5.1 Intel Core i9-10850K 3,6Ghz ................................................................................. 10
2.5.2 Apple A13 Bionic ................................................................................................... 11
2.5.3 ATxmega256A3U .................................................................................................. 11
2.5.4 GAP8 ..................................................................................................................... 12
2.5.5 PULPino ................................................................................................................. 12
3. DISEÑO DEL MICROPROCESADOR __________________________________ 13
3.1 Conjunto de instrucciones base RV32I .................................................................. 13
3.1.1 Listado de instrucciones ....................................................................................... 13
3.1.2 Formato de instrucciones ..................................................................................... 18
3.1.3 Disposición de la memoria ................................................................................... 18
3.2 Datapath ................................................................................................................. 20
3.2.1 PC y ROM .............................................................................................................. 20
Memoria
vi
3.2.2 Decodificador de instrucciones ............................................................................ 21
3.2.3 Banco de registros ................................................................................................. 23
3.2.4 Unidad aritmético-lógica ...................................................................................... 24
3.2.5 Operaciones con registros .................................................................................... 25
3.2.6 Operaciones con inmediatos ................................................................................ 26
3.2.7 RAM ....................................................................................................................... 26
3.2.8 Carga de memoria ................................................................................................. 27
3.2.9 Almacenamiento en memoria .............................................................................. 28
3.2.10 Suma del PC ........................................................................................................... 29
3.2.11 Transferencia de control condicional ................................................................... 30
3.2.12 Transferencia de control incondicional ................................................................ 31
3.2.13 Carga de inmediato U ........................................................................................... 32
3.2.14 E/S mapeado por memoria ................................................................................... 32
3.2.15 E/S Nexys 2 ............................................................................................................ 36
3.2.16 Datapath Completo ............................................................................................... 39
3.3 Unidad de control .................................................................................................. 40
3.3.1 Fetch ...................................................................................................................... 41
3.3.2 Operaciones entre registros ................................................................................. 42
3.3.3 Operaciones con inmediatos ................................................................................ 43
3.3.4 Carga de memoria ................................................................................................. 44
3.3.5 Almacenamiento en memoria .............................................................................. 45
3.3.6 Transferencia de control condicional ................................................................... 46
3.3.7 Transferencia de control incondicional ................................................................ 48
3.3.8 Carga de inmediato U ........................................................................................... 50
4. SIMULACIÓN DEL DISEÑO ________________________________________ 51
4.1 Testbench y valores iniciales ................................................................................. 51
4.2 Instrucciones de formato R ................................................................................... 53
4.2.1 Explicación del programa ...................................................................................... 53
4.2.2 Resultados de la simulación .................................................................................. 54
4.3 Instrucciones de formato I Computacionales ....................................................... 57
4.3.1 Explicación del programa ...................................................................................... 57
4.3.2 Resultados de la simulación .................................................................................. 58
4.4 Instrucciones de formato I ..................................................................................... 60
4.4.1 Explicación del programa ...................................................................................... 60
4.4.2 Resultados de la simulación .................................................................................. 61
4.5 Instrucciones de formato S .................................................................................... 65
Diseño, implementación y test de un core RISC-V
vii
4.5.1 Explicación del programa ..................................................................................... 65
4.5.2 Resultados de la simulación ................................................................................. 66
4.6 Instrucciones de formato B .................................................................................... 68
4.6.1 Explicación del programa ..................................................................................... 69
4.6.2 Resultados de la simulación ................................................................................. 70
4.7 Instrucciones de formato U ................................................................................... 73
4.7.1 Explicación del programa ..................................................................................... 73
4.7.2 Resultados de la simulación ................................................................................. 74
4.8 Instrucciones de formato J ..................................................................................... 75
4.8.1 Explicación del programa ..................................................................................... 75
4.8.2 Resultados de la simulación ................................................................................. 76
5. IMPLEMENTACIÓN DEL DISEÑO ___________________________________ 79
5.1 Síntesis .................................................................................................................... 80
5.1.1 Memoria de bloque .............................................................................................. 80
5.1.2 Informe de síntesis ............................................................................................... 81
6. ANÁLISIS DEL IMPACTO AMBIENTAL _______________________________ 83
7. CONCLUSIONES ________________________________________________ 85
8. ANÁLISIS ECONÓMICO ___________________________________________ 87
9. BIBLIOGRAFÍA __________________________________________________ 89
ANEXO A. DATAPATH ________________________________________________ 91
ANEXO B. DESCRIPCIÓN VHDL _________________________________________ 93
B.1 TOP ......................................................................................................................... 93
B.2 Datapath ................................................................................................................. 95
B.3 ALU ....................................................................................................................... 101
B.4 Data_SignExtend .................................................................................................. 102
B.5 Inst_Decoder ........................................................................................................ 102
B.6 Mem_DataInstr .................................................................................................... 103
B.7 Codificacion_Mem ............................................................................................... 108
B.8 Registro_IN ........................................................................................................... 110
B.9 Registro_OUT ....................................................................................................... 110
B.10 MUX2x1_5b .......................................................................................................... 111
B.11 RAM ...................................................................................................................... 111
Memoria
viii
B.12 ROM ..................................................................................................................... 113
B.13 Mux_4x1 .............................................................................................................. 114
B.14 Mux2x1_9b .......................................................................................................... 115
B.15 Mux2x1_32b ........................................................................................................ 115
B.16 Reg_Bank ............................................................................................................. 115
B.17 Registro_9b .......................................................................................................... 117
B.18 Out Nexys 2 .......................................................................................................... 117
B.19 Registro_32b ........................................................................................................ 119
B.20 UC ......................................................................................................................... 119
B.21 tb_TOP ................................................................................................................. 125
ANEXO C. TABLA SEÑALES DE CONTROL DE UC ___________________________ 127
ANEXO D. CODIFICACIÓN INSTRUCCIONES RV32I _________________________ 131
ANEXO E. FICHERO DE RESTRICCIONES ISE ______________________________ 133
Diseño, implementación y test de un core RISC-V
ix
Índice de figuras
Figura 1.3-1 Diagrama de Gantt donde se expone la programación temporal del proyecto. _____ 2
Figura 2.1-1 Arquitectura de Von Neuman. (2) ________________________________________ 4
Figura 2.1-2 Arquitectura de Harvard. (4) ____________________________________________ 4
Figura 2.2-1 Estructura de un sistema abierto basado en un microprocesador. (6) ____________ 5
Figura 2.3-1 Estructura de un microcontrolador. (6) ____________________________________ 8
Figura 2.4-1 Conjuntos base y extensiones de RISC-V. (9) _______________________________ 10
Figura 2.5-1 Procesador A13 de Apple. (10) __________________________________________ 11
Figura 2.5-2 Procesador GAP8 de GreenWaves. (11) ___________________________________ 12
Figura 3.1-1 Formato de las instrucciones del conjunto RV32I. ___________________________ 18
Figura 3.1-2 Direccionamiento de la memoria. _______________________________________ 19
Figura 3.1-3 Bloque de memoria RAM. _____________________________________________ 19
Figura 3.2-1 Registro “Contador de programa” y memoria ROM de instrucciones. ___________ 20
Figura 3.2-2 Bloque decodificador de instrucciones. ___________________________________ 21
Figura 3.2-3 Esquema eléctrico del decodificador de instrucciones. _______________________ 22
Figura 3.2-4 Banco de registros de RV32I. ___________________________________________ 23
Figura 3.2-5 ALU y registros A y B. _________________________________________________ 24
Figura 3.2-6 Escritura del resultado de la ALU en el banco de registros. ____________________ 25
Memoria
x
Figura 3.2-7 Adición de inmediato de tipo I. _________________________________________ 26
Figura 3.2-8 Memoria RAM de datos. ______________________________________________ 26
Figura 3.2-9 Lectura de datos de la memoria RAM. ___________________________________ 27
Figura 3.2-10 Bloque encargado de adaptar el dato de la memoria. ______________________ 28
Figura 3.2-11 Escritura de datos en la memoria RAM. _________________________________ 28
Figura 3.2-12 Adición de cableado que permite pasar a la siguiente instrucción. ____________ 29
Figura 3.2-13 Adición del inmediato B y el estado zero para las instrucciones de transferencia de control
condicional. _____________________________________________________________ 30
Figura 3.2-14 Adición del inmediato J para las instrucciones de transferencia de control incondicional.
_______________________________________________________________________ 31
Figura 3.2-15 Carga de inmediato U. _______________________________________________ 32
Figura 3.2-16 Mapeado de la memoria de datos e instrucciones. ________________________ 32
Figura 3.2-17 Disposición de la memoria. ___________________________________________ 33
Figura 3.2-18 Incorporación de los registros E/S mapeados por memoria con los componentes de la
placa Nexys 2.____________________________________________________________ 36
Figura 3.2-19 Disposición de los interruptores de la placa Nexys 2. (14) ___________________ 37
Figura 3.2-20 Funcionamiento del monitor de siete segmentos de la placa Nexys 2. (14) ______ 37
Figura 3.2-21 Cronograma de sincronización de las señales SSEG y AN. (14) ________________ 38
Figura 3.2-22 Adaptación de los puertos E/S a la placa Nexys 2. _________________________ 39
Figura 3.2-23 Esquema completo del datapath. ______________________________________ 39
Diseño, implementación y test de un core RISC-V
xi
Figura 3.3-1 Diagrama de estados de la operación fetch. _______________________________ 41
Figura 3.3-2 Diagrama de estados de las operaciones entre registros. _____________________ 42
Figura 3.3-3 Diagrama de estados de las operaciones con inmediatos._____________________ 43
Figura 3.3-4 Diagrama de estados de la carga de memoria. _____________________________ 44
Figura 3.3-5 Diagrama de estados del almacenamiento en memoria. ______________________ 45
Figura 3.3-6 Diagrama de estados de transferencia de control condicional. _________________ 46
Figura 3.3-7 Diagrama de estados de transferencia de control incondicional. _______________ 48
Figura 3.3-8 Diagrama de estados de carga de inmediato U. _____________________________ 50
Figura 4.2-1 Primera fila de resultados de la simulación de las instrucciones de formato R. _____ 54
Figura 4.2-2 Segunda fila de resultados de la simulación de las instrucciones de formato R. ____ 55
Figura 4.2-3 Captura de los valores del banco de registro una vez completadas todas las instrucciones
de formato R en la simulación. _______________________________________________ 56
Figura 4.3-1 Primera fila de resultados de la simulación de las instrucciones de formato I
computacionales. _________________________________________________________ 58
Figura 4.3-2 Segunda fila de resultados de la simulación de las instrucciones de formato I
computacionales. _________________________________________________________ 59
Figura 4.3-3 Captura de los valores del banco de registro una vez completadas todas las instrucciones
de formato I computacionales en la simulación. _________________________________ 59
Figura 4.4-1 Primera página de la primera fila de resultados de la simulación de las instrucciones de
formato I. _______________________________________________________________ 61
Figura 4.4-2 Segunda página de la primera fila de resultados de la simulación de las instrucciones de
formato I. _______________________________________________________________ 62
Memoria
xii
Figura 4.4-3 Primera página de la segunda fila de resultados de la simulación de las instrucciones de
formato I. _______________________________________________________________ 63
Figura 4.4-4 Segunda página de la segunda fila de resultados de la simulación de las instrucciones de
formato I. _______________________________________________________________ 64
Figura 4.4-5 Captura de los valores del banco de registro una vez completadas todas las instrucciones
de formato I en la simulación. _______________________________________________ 65
Figura 4.5-1 Resultados de la simulación de las instrucciones de formato S. ________________ 66
Figura 4.5-2 Captura de los valores de la memoria RAM 0 una vez completadas las instrucciones de
formato S.. ______________________________________________________________ 67
Figura 4.5-3 Figura 4.10. Captura de los valores de la memoria RAM 1 una vez completadas las
instrucciones de formato S. _________________________________________________ 67
Figura 4.5-4 Captura de los valores de la memoria RAM 2 una vez completadas las instrucciones de
formato S. ______________________________________________________________ 67
Figura 4.5-5 Captura de los valores de la memoria RAM 3 una vez completadas las instrucciones de
formato S. ______________________________________________________________ 67
Figura 4.6-1 Primera fila de resultados de la simulación de las instrucciones de formato B. ____ 70
Figura 4.6-2 Segunda fila de resultados de la simulación de las instrucciones de formato B. ____ 71
Figura 4.6-3 Tercera fila de resultados de la simulación de las instrucciones de formato B. ____ 72
Figura 4.7-1 Resultados de la simulación de las instrucciones de formato U. ________________ 74
Figura 4.7-2 Captura de los valores del banco de registro una vez completadas todas las instrucciones
de formato U en la simulación. ______________________________________________ 75
Figura 4.8-1 Resultados de la simulación de las instrucciones de formato J. ________________ 76
Diseño, implementación y test de un core RISC-V
xiii
Figura 4.8-2 Captura de los valores del banco de registro una vez completadas todas las instrucciones
de formato J en la simulación. _______________________________________________ 77
Figura 5-1 FPGA Nexys 2 de la empresa DIGILENT. (14) _________________________________ 79
Figura 5.1-1 Captura de los resultados del informe de temporización del programa Xilinx ISE. __ 82
Diseño, implementación y test de un core RISC-V
xv
Índice de tablas
Tabla 3.1-1 Instrucciones computacionales del conjunto RV32I. __________________________ 14
Tabla 3.1-2 Instrucciones de acceso a la memoria del conjunto RV32I._____________________ 16
Tabla 3.1-3 Instrucciones de transferencia de control del conjunto RV32I. __________________ 17
Tabla 3.2-1 Operaciones ejecutadas por la ALU (“u”: sin signo, “s”: con signo). ______________ 25
Tabla 3.2-2 Comportamiento del codificador usado para la señal enable del registro del dispositivo de
salida. __________________________________________________________________ 34
Tabla 3.2-3 Comportamiento del codificador usado para las señales enable de la memoria RAM. 35
Tabla 3.2-4 Esquema de sincronización de las señales SSEG y AN. ________________________ 38
Tabla 3.3-1 Función de las señales de control y estado. ________________________________ 40
Tabla 3.3-2 Valor de las señales de control para los estados S0 y S11. ______________________ 41
Tabla 3.3-3 Valor de las señales de control para los estados del S2 al S12. __________________ 43
Tabla 3.3-4 Valor de las señales de control para el estado S13. ___________________________ 44
Tabla 3.3-5 Valor de las señales de control para los estados del S25 al S30. _________________ 45
Tabla 3.3-6 Valor de las señales de control para los estados del S31 al S34. _________________ 46
Tabla 3.3-7 Esquema de operación y resultado de la transferencia de control condicional. _____ 47
Tabla 3.3-8 Valor de las señales de control para los estados del S18 al S24. _________________ 48
Tabla 3.3-9 Valor de las señales de control para los estados S16 y S17. ____________________ 49
Tabla 4.1-1 Valor inicial de las direcciones de la memoria RAM indicadas. __________________ 52
Memoria
xvi
Tabla 4.1-2 Valor inicial del banco de registros. ______________________________________ 52
Tabla 4.2-1 Programa usado para simular las instrucciones de formato R.2 _________________ 53
Tabla 4.3-1 Programa usado para simular las instrucciones de formato I Computacionales. ____ 57
Tabla 4.4-1 Programa usado para simular las instrucciones de formato I. __________________ 60
Tabla 4.5-1 Programa usado para simular las instrucciones de formato I. __________________ 65
Tabla 4.6-1 Programa usado para simular las instrucciones de formato B. (“u”: sin signo, “s”: con signo).
_______________________________________________________________________ 69
Tabla 4.7-1 Programa usado para simular las instrucciones de formato U. _________________ 73
Tabla 4.8-1 Programa usado para simular las instrucciones de formato U. (solo se muestra la operación
de las instrucciones importantes) ____________________________________________ 75
Tabla 5.1-1 Esquema con las principales diferencias entre memoria distribuida o de bloque. (15) 80
Tabla 5.1-2 Tabla resumen del uso de dispositivos del programa Xilinx ISE. _________________ 81
Diseño, implementación y test de un core RISC-V
1
1. Introducción
1.1. Motivación
El diseño del núcleo de un microcontrolador es parte esencial de la arquitectura de computadoras, una
rama que precisa del conocimiento previo sobre electrónica digital, la cual se estudia durante la carrera
de ingeniería electrónica industrial y automática. Como los microcontroladores se encuentran en la
mayoría de dispositivos electrónicos actuales, su diseño está en constante mejora.
Es por esto que, en este trabajo, además de reunir gran parte de los conocimientos adquiridos durante
el grado universitario, ese necesaria la aplicación de juicio propio y creatividad con tal de que, teniendo
en cuenta las limitaciones que pueden surgir, se puedan cumplir los objetivos marcados.
Además, la implementación precisa usar VHDL y una placa FPGA, por lo tanto, se adentra más en lo
estudiado durante el grado universitario.
Por otro lado, este proyecto se basa en la librería RISC-V, de código abierto, lo que abre paso a una
colaboración abierta y práctica ya que su código fuente es de acceso gratuito. Es por esto que la
construcción de un microcontrolador con código abierto con un nivel de dificultad adaptado al de un
estudiante puede dar paso a que en el futuro se creen proyectos universitarios basándose en este.
1.2. Objetivos del trabajo
Para que este proyecto esté acorde a lo que se explica en el resumen, se reúnen un seguido de
objetivos que alcanzar para poder proseguir:
• Documentar sobre el funcionamiento de microcontroladores y microprocesadores
• Comprender las instrucciones del set con el que se trabajará, RISC-V, además de su codificación
• Diseñar el datapath
• Generar la unidad de control
• Programar en VHDL
• Desarrollar una aplicación sencilla y viable
• Simular el microprocesador
• Implementar en una FPGA
Memoria
2
1.3. Alcance del trabajo
Con los objetivos marcados ya se puede definir el alcance del trabajo, pero se tienen en cuenta otros
factores.
Por un lado, un punto principal de este diseño es que es educativo, queriendo decir con esto que no
tendrá un nivel competitivo en el mercado y sus prestaciones serán mucho más bajas de lo que se
encuentra en diseños actuales. Por esto mismo, también hay que anotar que los recursos serán muy
limitados, no solo material y presupuesto, sino también en el hecho de que tan solo trabaja en este
proyecto el autor del trabajo, contando con la ayuda del director, a diferencia de las empresas, donde
son equipos de ingenieros titulados los que se encargan del diseño.
Por el otro lado, no se puede establecer correctamente un alcance sin una programación temporal.
Para esto, se ha generado un diagrama de Gantt, usando la web “Lucidchart”, donde se especifica
cuando se trabajará en cada objetivo y, con ello, que debe realizarse al finalizar cada uno.
Figura 1.3-1 Diagrama de Gantt donde se expone la programación temporal del proyecto.
El alcance de este proyecto es, pues, generar un microprocesador con el conjunto de instrucciones
RISC-V funcional y capaz de realizar una aplicación sencilla, tanto en simulación como implementado
sobre una FPGA.
Diseño, implementación y test de un core RISC-V
3
2. Microcontroladores
En este capítulo, se hace un resumen teórico sobre los microcontroladores, los tipos que hay y su
estado del arte. Se introduce además el código abierto y se explica su importancia.
2.1. Computadores
Un computador es, en grandes rasgos, un sistema digital cuyo objetivo es, a través del flujo de datos,
manipular información para elaborar un resultado más complejo como, por ejemplo, el resultado de
un problema matemático.
Todo computador se organiza en una estructura que separa los elementos que lo forman dependiendo
de su función y en las interconexiones que existen entre ellos. Estos elementos básicos se modulan en:
procesador, dispositivos de entrada, dispositivos de salida y dispositivos de entrada/salida, formando
así los elementos de la ecuación de su funcionamiento:
Datos de entrada + procesado = datos de salida
El procesador es el núcleo del computador, y es el que ejecuta el programa. Definimos un programa
(1) como la secuencia de instrucciones que manipulan la información para obtener el resultado. Este
se puede generar en cualquier idioma de programación, el cual es entendible para el humano, y luego
se traduce a lenguaje máquina, formado por vectores de números binarios que el computador puede
interpretar.
El procesador se divide en la unidad central de proceso, conocida como CPU, y la memoria. La unidad
central de proceso se divide a su vez en la unidad de proceso, que genera las instrucciones, y la unidad
de control, que se encarga de determinar la secuencia de instrucciones que forman el programa en su
correcto orden.
2.1.1 Arquitectura de Von Neuman y arquitectura de Harvard
La arquitectura de las computadoras es el diseño de la estructura del sistema, que define como se
dividen y comunican los elementos que lo forman. Los computadores actuales están basados en la
arquitectura de Von Neuman.
Memoria
4
Figura 2.1-1 Arquitectura de Von Neuman. (2)
Esta arquitectura consta de memoria, una unidad central de proceso dividida en una unidad de control
y una unidad aritmético-lógica, conocida como ALU o UAL, y las unidades de entrada y salida. Las
flechas representan buses de datos y direcciones. La característica principal de esta arquitectura es la
memoria, la cual almacena tanto los datos como el programa. Esto causa lo que se denomina “cuello
de botella Von Neumann” debido a que no se puede buscar instrucciones y operar simultáneamente
ya que comparten un mismo bus. Por otro lado, la unidad de control es relativamente simple. (3)
La arquitectura de Harvard se diferencia principalmente por disponer de una memoria dedicada
exclusivamente a las instrucciones y otra a los datos.
Figura 2.1-2 Arquitectura de Harvard. (4)
Esta diferencia ayuda a corregir la principal limitación de la arquitectura de Von Neumann y permite
operar con datos a la vez que se accede a instrucciones. Esta arquitectura se aplica en varios
microcontroladores aprovechando que es más rápida para un circuito complejo. (5)
Diseño, implementación y test de un core RISC-V
5
2.2. Microprocesadores
El microprocesador es una unidad central de procesamiento contenida en un solo circuito integrado,
o chip. Su función es interpretar y elaborar datos en función de un programa. La información que sale
de la CPU se envía a distintos periféricos externos a este chip, como memorias o puertos de salida.
Figura 2.2-1 Estructura de un sistema abierto basado en un microprocesador. (6)
2.2.1 Ciclo de trabajo
La CPU funciona en base a un ciclo de trabajo, llamado ciclo de instrucción, en el que obtiene y ejecuta
las instrucciones que obtiene de la memoria. Se divide en tres fases: la primera, fetch, es obtener la
instrucción ubicada en la dirección de la memoria marcada por un registro llamado contador de
programa (PC); la segunda, decode, es descodificar la instrucción obtenida e incrementar el PC para
que en el siguiente ciclo de trabajo se adquiera la instrucción correspondiente; la tercera, execute, es
la ejecución de la instrucción y la actualización de los datos de la memoria. La CPU trabaja ejecutando
estos ciclos seguidamente guiada por la señal de reloj, o clock, la cual es de un solo bit y cambia de
manera periódica.
2.2.2 Unidad de ejecución y unidad de control
Como ya se ha especificado, el procesador se divide en la unidad de ejecución y la de control. La unidad
de ejecución lo conforma el decodificador de instrucciones, que separa los bits correspondientes de la
instrucción para que actúen donde deben, y la unidad aritmético-lógica, que se encarga de ejecutar las
instrucciones aritméticas y lógicas, como su nombre indica.
Memoria
6
La unidad de control es la encargada de procesar el programa tras la operación de decode para
temporizar y organizar la ejecución de las instrucciones. Las instrucciones son un vector de bit que
contiene la información necesaria: que operación se va a efectuar y sobre que registros o números.
Una vez descodificada, el vector que indica la operación va a la unidad de control como señal de estado
y esta se encarga enviar señales de control a los elementos de la unidad de ejecución para que se pueda
llevar a cabo. La estructura de esta unidad se basa en el tipo de microarquitectura.
2.2.3 Conjunto de instrucciones, ISA
El conjunto de instrucciones, llamado ISA del inglés “Instruction Set Architecture”. Si definimos las
instrucciones como las palabras del lenguaje del ordenador, el conjunto de instrucciones sería su
vocabulario, ya que incluye todas las instrucciones que la CPU puede entender y ejecutar. (7)
Para ayudar a la comprensión por parte del usuario, las instrucciones se encuentran en lenguaje
ensamblador, un lenguaje que se usa para representar el lenguaje máquina, formado por números
binarios y que la CPU sí puede procesar. El lenguaje ensamblador hace uso de mnemónicos, que son
palabras que sustituyen al código de una operación, y para direcciones de registros o números, usa la
notación decimal.
Presenta, además, la codificación que se usa para cada tipo de instrucción, para así determinar que se
representa dentro del vector de bits de cada instrucción.
Dos tipos de ISA son: CISC (“Complex Instruction Set Computer”) y RISC (“Reduced Instruction Set
Computer”). Los procesadores RISC, el tipo con el que se trabaja en este proyecto, se caracterizan por
tener instrucciones cortas y un procesamiento sencillo, a diferencia de CISC, donde las instrucciones
son más complejas. Esto significa que, para operaciones complejas, el tipo RISC debe implementar un
seguido de instrucciones mientras que un CISC podría hacerlo solo con una, pero, por contrapartida,
minimizan la complejidad de la estructura consiguiendo así mayor velocidad y facilidad para ejecutar
instrucciones simultáneamente. (8)
2.2.4 Microarquitectura
La microarquitectura es la implementación de un conjunto de instrucciones en una CPU. Esta presenta
una estructura formada por la memoria o memorias de instrucciones y datos, el contador de programa,
el decodificador de instrucciones, la unidad aritmético-lógica y el resto de elementos necesarios. Estos
elementos son en función del conjunto de instrucciones, ya que la microarquitectura se conforma de
los elementos necesarios para poder ejecutar todas las instrucciones de la microarquitectura.
Diseño, implementación y test de un core RISC-V
7
Además de los elementos, se encuentran también los buses de datos y las señales. Las señales pueden
ser de estado, en caso de que sean una salida cuya función es informar a la unidad de control, o de
control, que son de entrada y es la respuesta que indica a los elementos como proceder.
Para un mismo conjunto de instrucciones se puede tener diferentes microarquitecturas, que difieren
en complejidad o coste. Existen muchos tipos de microarquitectura, entre las cuales hay tres sencillas:
mono ciclo, multi ciclo y pipeline.
Las diferencias entre estos tipos de microarquitectura es la manera en la que se conectan los elementos
y van en función de los números de ciclos por instrucción, llamado CPI.
Una microarquitectura mono ciclo ejecuta una instrucción entera por cada ciclo de reloj, haciendo así
que la frecuencia de ciclo viene determinada por la instrucción más larga del conjunto. En el caso del
multi ciclo, en cada ciclo de reloj se ejecuta una de las fases de la instrucción (fetch, decode, execute,
memory y writeback), de manera que la estructura es más compleja, pero consigue mayor frecuencia
de reloj, lo que se traduce como mayor velocidad de ejecución. La microarquitectura pipeline, funciona
como la multi ciclo, pero aprovecha el hecho de que cada fase de la instrucción usa distintos elementos
de la microarquitectura para llevar a cabo más de una operación simultáneamente. Para ello, se
colocan registros entre los elementos de cada fase para poder guardar los resultados. Es el tipo de
arquitectura más compleja, pero a su vez logra completar un mayor número de instrucciones en menor
tiempo.
2.3. Microcontroladores
Un microcontrolador es un dispositivo electrónico que puede hacer procesos lógicos. Está formado por
un microprocesador y todos los elementos necesarios de un computador, para trabajar, así como un
sistema. Todo esto está contenido en un solo chip, por lo tanto, consigue reducir enormemente el
espacio en comparación con un sistema en el que encontramos un microprocesador y el resto de
elementos externos a él.
Memoria
8
Figura 2.3-1 Estructura de un microcontrolador. (6)
Estos elementos necesarios dependen en función de la utilidad que se quiera dar al microcontrolador.
Algunos de los más frecuentes son memorias de tipo RAM o ROM, circuito de entradas y salidas y
decodificadores.
Al tener todos los elementos necesarios integrados, un microcontrolador ya es un dispositivo útil por
sí mismo y lo encontramos en muchos campos de aplicación, como computadoras, smartphones,
coches, electrodomésticos, robótica, etc.
2.4. RISC-V
RISC-V es un conjunto de instrucciones de hardware de código abierto, lo que significa que sus
especificaciones son de acceso público, desarrollado por en la Universidad de California en Berkeley. A
continuación, se introduce la arquitectura usada para el diseño del microcontrolador de este trabajo.
Según la guía práctica de RISC-V (9), su objetivo es convertirse en una ISA universal, apta para todo tipo
de procesadores y softwares, mejorando así enormemente la compatibilidad. Para ello debe acoplarse
tanto a microcontroladores como cualquier otro sistema computacional, funcionar bien con lenguajes
de programación e implementarse correctamente en cualquier tecnología. Además, debe ser estable
y eficiente en cualquier microarquitectura.
Para conseguir esta empresa, muchas universidades y proyectos colaboran con RISC-v para mejorarlo
de manera descentralizada. Una de sus mayores ventajas es su diseño modular, ya que cuenta con un
núcleo de instrucciones estables y un conjunto de extensiones las cuales cambian sus prestaciones y
se pueden añadir en función del uso que se le quiera dar a la implementación.
Diseño, implementación y test de un core RISC-V
9
2.4.1 Código abierto
Normalmente, el código de cualquier software tiene derecho de autor, por lo que se debe pagar una
cantidad determinada de dinero para su explotación. Esto se refiere a ver, modificar o distribuir el
código que lo genera, siendo su uso un tema aparte. Un ejemplo sería un sistema operativo como
Windows, en el que el usuario adquiere por dinero una copia para su uso, pero aun así no puede
acceder a su código, el cual está reservado para los administradores de la empresa.
Se conoce como código abierto todo aquel en el que su acceso es para todo el público y no hay
limitaciones en que utilidades se le puede dar. Esto genera una gran ventaja en varios términos, puesto
que la mayoría de estos códigos se desarrolla de manera descentralizada y permite una gran ventaja a
distintos sectores, como por ejemplo el académico.
Hay un gran número de programas, como navegadores o antivirus, de código abierto y también se
incluyen varios conjuntos de instrucciones de hardware. Uno de los más destacables es OpenRisc, el
cual es de tipo RISC. Basándose en conjuntos como este y en DLX, una arquitectura RISC, nace RISC-V.
2.4.2 Convención de nombres de conjuntos base y extensiones
Existen 5 tipos de conjuntos bases, aunque solo 3 están ratificadas, dos de las cuales son RV32I para
cuando se trabaja con 32 bits y RV64I para cuando se trabaja con 64 bits.
En cuanto a la nomenclatura de estos conjuntos de instrucciones, “RV” es una abreviación de RISC-V,
el número indica el ancho de bits con el que trabaja el banco de registros y el resto de letras con qué
tipo de dato trabaja.
La I hace referencia a “Integer” (entero en castellano) y es el tipo de dato con el que se trabaja en estos
conjuntos base.
Las extensiones son más conjuntos de instrucciones que se añaden al conjunto base seleccionado y
que se identifican con una letra mayúscula. Todos estos añaden un tipo de dato al conjunto de
instrucciones y son datos atómicos (“A”), de multiplicación(“M”) y de coma flotante de precisión simple
(“F”) y doble (“D”).
El conjunto final usado se representa con el nombre del conjunto base y de las extensiones escogidas,
por lo que, por ejemplo, un conjunto de instrucciones RISC-V de 32-bits al que se le añade la extensión
de multiplicación y división de enteros (representada con una M) se denomina RV32IM.
Memoria
10
Figura 2.4-1 Conjuntos base y extensiones de RISC-V. (9)
2.5. Estado del arte
2.5.1 Intel Core i9-10850K 3,6Ghz
Intel es, junto con AMD, la marca líder de procesadores en el campo de la computación. El último
producto de la marca, parte de la familia i9 de la décima generación cuenta con una frecuencia de 3,6
GHz (hasta 5,1 GHz), 10 núcleos de procesador y soporta un ancho de banda de memoria de 45,8 GB/s.
Puede ejecutar 20 subprocesos simultáneamente.
Está basado en la arquitectura Intel 64 y ofrece un procesamiento de 64 bits. Esta arquitectura de tipo
CISC es la implementación por parte de la empresa de la tecnología x86, la arquitectura más común en
los ordenadores actuales.
Diseño, implementación y test de un core RISC-V
11
2.5.2 Apple A13 Bionic
La marca de smartphones crea sus propios procesadores, compitiendo siempre con los modelos
líderes. El último modelo es un procesador de 7 nanómetros que cuenta con 8 núcleos llegando a poder
ejecutar un billón de operaciones por segundo.
Figura 2.5-1 Procesador A13 de Apple. (10)
El chip es de 64 bits y está basado en la arquitectura ARM, que es de tipo RISC, ya que así facilita poder
tener un tamaño tan reducido y trabajar con baterías de menor capacidad, en comparación a las de un
ordenador.
2.5.3 ATxmega256A3U
El ATxmega256A3U es un microcontrolador de 8 o 16 bits con una velocidad de 32 MHz. Es un ejemplo
de microcontrolador que no entra en el gran mercado competitivo de ordenadores o smartphones,
sino que entra en campos como construcción, industria o automoción. El grupo de microcontroladores
ATxmega forman parte de la familia AVR, de tipo RISC y perteneciente a el fabricante Atmel.
AVR es una arquitectura RISC y sus microarquitecturas se basan en la arquitectura Hardvard.
Memoria
12
2.5.4 GAP8
La empresa GreenWaves creó en 2018 GAP8, un microprocesador de 9 núcleos, 32 bits y basado en la
ISA RISC-V diseñado para el mercado IoT (“Internet of Things”), el cual engloba todos los dispositivos
conectados a internet, y para el denominado Edge Computing, que es la intención de que estos
dispositivos analicen y actúen por su propia cuenta.
Figura 2.5-2 Procesador GAP8 de GreenWaves. (11)
Este microprocesador está basado en el Open Core de la plataforma PULP, la cual implementa diversos
cores RISC-V, tiene una frecuencia de 250MHz. Sus características quedan muy por debajo de los de
más alta gama debido a que está diseñado para integrarse en dispositivos más reducidos y focalizados,
como el procesado de señales o la inteligencia artificial.
2.5.5 PULPino
PULP (“Parallel Ultra Low Power”) es un proyecto creado entre ISS (“Integrated System Laboratory”)
de Zurich y EEES (“Energy-efficient Embedded Systems”) de la universidad de Bolonia en 2013 con el
objetivo de desarrollar hardware y software de código abierto que sirva para mejorar la eficiencia de
la energía y para las nuevas demandas del IoT Market. (12)
PULPino es un microcontrolador de un núcleo basado en RISC-V, por lo que es de código abierto. Tiene
una microarquitectura de tipo pipeline y tiene soporte con las extensiones RV32I, RV32C y RV32M,
además de poder configurarse para usar la RV32F. (13)
Igual que la GAP8, PULPino es un ejemplo de microcontrolador de código abierto con diversas
aplicaciones, lo que permite a cualquier persona implementarlo.
Diseño, implementación y test de un core RISC-V
13
3. Diseño del microprocesador
En este capítulo se presenta el conjunto base con el que se creará la microarquitectura y se hace un
estudio paso a paso de como se ha generado el diseño.
3.1 Conjunto de instrucciones base RV32I
El conjunto de instrucciones base RV32I es suficiente para poder operar con sistemas y busca reducir
el mínimo hardware requerido. A partir de este se puede extender para trabajar con registros de 16
bits además de los de 32 y se puede añadir extensiones para usar tipos de datos además de enteros
(véase Figura 2.4-1)
Para este trabajo se usa el modo no privilegiado de RISC-V, el cual se limita a operaciones con el banco
de registro y lecturas y escrituras con la memoria, pues tiene un acceso restringido a los recursos del
sistema, como serían las interrupciones.
Para RV32I se usa un banco de 32 registros, cada uno de un ancho de 32 bits. Para referirse a estos se
usa una ‘x’ seguido del número de su dirección (x25 sería el registro en la dirección 25, por ejemplo).
El registro x0 contiene un valor constante 0 y los 31 restantes son de propósito general.
Según las especificaciones de la ISA no hay un registro dedicado para el puntero de pila o la dirección
de retorno como ocurre en otros casos, pero sigue la convención estándar de llamada a software: x2
como el puntero de pila y x1 guarda la dirección de retorno de una función con x5 disponible para una
dirección alternativa.
3.1.1 Listado de instrucciones
RV32I cuenta con un total de 40 instrucciones, aunque puede ser reducido a 38. En este caso se ha
optado por la versión reducida donde no se usan las instrucciones “EBREAK”, “ECALL” y la instrucción
“FENCE” se cambia por la instrucción “NOP”. La instrucción “NOP” no se encuentra en la tabla ya que
se codifica como “ADDI x0, x0, 0” y sirve para incrementar el contador de programa sin modificaciones.
Existen 6 formatos de instrucciones que varían según el uso de registros fuente y destino y de valores
inmediatos. Cuatro de ellos son formatos estándar (R, I, S y U) y los otros dos son variaciones de otro
formato (B, o SB, es una variación del formato S y J, o UJ, es una variación del formato U) (véase Figura
3.1-1)
Memoria
14
Las variaciones son instrucciones relacionadas con saltos en el programa y, puesto que cada instrucción
está separada 4 direcciones (véase capítulo 3.1.3) haciendo que el bit de menos peso no sea necesario
(el segundo bit de menos peso se reserva para la extensión de 16 bits), estos cambian la disposición
del valor inmediato (véase capítulo 3.1.2).
Las instrucciones computacionales hacen operaciones entre registros fuente (rs1, rs2) o con valores
inmediatos (imm) y guardan su resultado en un registro destino (rd). En la tabla 3.1 se muestran
ordenados según el formato que tiene la instrucción y el tipo de operación que hacen.
Tabla 3-1 Instrucciones computacionales del conjunto RV32I.
INSTRUCCIONES COMPUTACIONALES
Instrucción Tipo Ensamblador Formato Operación Explicación
ADD Aritmética add rd, rs1, rs2 R rd <- rs1 + rs2 Suma dos registros
SUB Aritmética sub rd, rs1, rs2 R rd <- rs1 - rs2 Resta dos registros
SLL Desplazamiento sll rd, rs1, rs2 R rd <- rs1 <<
rs2
Desplazamiento lógico a la izquierda del registro rs1 un
número rs2 de bits.
SLT Comparación slt rd, rs1, rs2 R rd <- 1 si rs1 < rs2 si no 0
Compara dos registros como complemento a 2
SLTU Comparación sltu rd, rs1, rs2 R rd <- 1 si rs1 <u rs2 si no 0
Compara dos registros como binario natural
XOR Lógica xor rd, rs1, rs2 R rd <- rs1 ^ rs2 Operación XOR bit a bit entre
dos registros
SRL Desplazamiento srl rd, rs1, rs2 R rd <- rs1 >>
rs2
Desplazamiento lógico a la derecha el registro rs1 un
número rs2 de bits.
SRA Desplazamiento sra rd, rs1, rs2 R rd <- rs1 >>s
rs2
Desplazamiento aritmético a la derecha el registro rs1 un
número rs2 de bits.
OR Lógica or rd, rs1, rs2 R rd <- rs1 | rs2 Operación OR bit a bit entre dos
registros
AND Lógica and rd, rs1, rs2 R rd <- rs1 &
rs2 Operación AND bit a bit entre
dos registros
ADDI Aritmética addi rd, rs1,
imm I
rd <- rs1 + imm
Suma un registro y una constante en complemento a 2
SLTI Comparacion slti rd, rs1,
imm I
rd <- 1 si rs1 < rs2 si no 0
Compara un registro y una constante en complemento a 2
SLTIU Comparacion sltiu rd, rs1,
imm I
rd <- 1 si rs1 <u rs2 si no 0
Compara un registro y una constante como binario natural
XORI Lógica xori rd, rs1,
imm I
rd <- rs1 ^ imm
Operación XOR bit a bit entre un registro y una constante
ORI Lógica ori rd, rs1,
imm I
rd <- rs1 | imm
Operación OR bit a bit entre un registro y una constante
ANDI Lógica andi rd, rs1,
imm I
rd <- rs1 & imm
Operación AND bit a bit entre un registro y una constante
Diseño, implementación y test de un core RISC-V
15
SLLI Desplazamiento slli rd, rs1,
imm I
rd <- rs1 << imm
Desplazamiento lógico a la izquierda del registro rs1 un número constante de bits.
SRLI Desplazamiento srli rd, rs1,
imm I
rd <- rs1 >> imm
Desplazamiento lógico a la derecha del registro rs1 un número constante de bits.
SRAI Desplazamiento srai rd, rs1,
imm I
rd <- rs1 >>s imm
Desplazamiento aritmético a la derecha del registro rs1 un número constante de bits.
LUI Carga lui rd, imm U rd <-
imm*2^12
Carga la constante imm en los 20 bits de más peso del registro
destino llenando el resto con '0's
AUIPC Carga auipc rd, imm U rd <-
imm*2^12 + PC
Suma la constante imm, añadiendo 12 '0's a su derecha,
al contador de programa y lo guarda en el registro destino
Las instrucciones de acceso a la memoria se dividen en las de carga y las de almacenamiento. Las
instrucciones de carga leen el contenido de una dirección de la memoria de datos y la cargan en un
registro. Se pueden leer datos de 8, 16 o 32 bits, y estos se extienden puesto que el dato de entrada
de los registros debe ser de 32 bits.
En el caso de las instrucciones de almacenamiento, se guarda el contenido de un registro, del cual se
puede seleccionar entero o los 16 o 8 bits de menos peso, y se guarda en la dirección marcada de la
memoria. En este caso no es necesario extender puesto que la memoria está preparada para trabajar
con un ancho de 8 bits.
Memoria
16
Tabla 3-2 Instrucciones de acceso a la memoria del conjunto RV32I.
INSTRUCCIONES DE ACCESO A LA MEMORIA
Instrucción Tipo Ensamblador Formato Operación Explicación
LB Carga lb rd,
offset(rs1) I
Cargar byte
Carga el contenido del byte de la dirección de la
memoria rs1+offset en el registro destino
extendiendo a 32 bits con signo
LH Carga lh rd,
offset(rs1) I
Cargar media
palabra
Carga el contenido de los 2 bytes de la dirección de la memoria rs1+offset en el
registro destino extendiendo a 32 bits con
signo
LW Carga lw rd,
offset(rs1) I
Cargar palabra
Carga el contenido de los 4 bytes de la dirección de la memoria rs1+offset en el
registro destino
LBU Carga lbu rd,
offset(rs1) I
Cargar byte sin
signo
Carga el contenido del byte de la dirección de la
memoria rs1+offset en el registro destino
extendiendo a 32 bits sin signo
LHU Carga lhu rd,
offset(rs1) I
Cargar media
palabra sin signo
Carga el contenido de los 2 bytes de la dirección de la memoria rs1+offset en el
registro destino extendiendo a 32 bits sin
signo
SB Almacenamiento sb rs2,
offset(rs1) S
Almacenar byte
Guarda los 8 bits de menos peso de rs2 en el byte de la
dirección de la memoria rs1+offset
SH Almacenamiento sh rs2,
offset(rs1) S
Almacenar media
palabra
Guarda los 16 bits de menos peso de rs2 en los 2 bytes de la dirección de la
memoria rs1+offset
SW Almacenamiento sw rs2,
offset(rs1) S
Almacenar palabra
Guarda el contenido de rs2 en los 4 bytes de la
dirección de la memoria rs1+offset
Diseño, implementación y test de un core RISC-V
17
Por último, las instrucciones de transferencia de control permiten hacer saltos en el programa para
generar sentencias condicionales o bucles.
Tabla 3-3 Instrucciones de transferencia de control del conjunto RV32I.
INSTRUCCIONES DE TRANSFERENCIA DE CONTROL
Instrucción Tipo Ensamblador Formato Operación Explicación
BEQ Comparación beq rs1, rs2,
offset B
Saltar si son
iguales
Si el contenido de rs1 y rs2 es igual el contador de programa pasa a la instrucción separada
por offset
BNE Comparación bne rs1, rs2,
offset B
Saltar si no son iguales
Si el contenido de rs1 y rs2 no es igual el contador de
programa pasa a la instrucción separada por offset
BLT Comparación blt rs1, rs2,
offset B
Saltar si es menor
que
Si el contenido de rs1 es menor que el de rs2 el contador de
programa pasa a la instrucción separada por offset
BGE Comparación bge rs1, rs2,
offset B
Saltar si es mayor o igual que
Si el contenido de rs1 es mayor o igual que el de rs2 el
contador de programa pasa a la instrucción separada por
offset
BLTU Comparación bltu rs1, rs2,
offset B
Saltar si es menor que sin signo
Si el contenido de rs1 es menor que el de rs2 (sin signo) el
contador de programa pasa a la instrucción separada por
offset
BGEU Comparación bgeu rs1, rs2, offset
B
Saltar si es mayor o igual que sin signo
Si el contenido de rs1 es mayor o igual que el de rs2 (sin signo) el contador de programa pasa a la instrucción separada por
offset
JAL Salto jal rd, offset J Saltar y enlazar
Introduce en el PC la dirección de esta instrucción sumada al offset y guarda en Rd la que
venía después
JALR Salto jalr rd, rs1,
offset I
Saltar y enlazar registro
Introduce en el contador de programa la direccion de
rs1+offset y aguarda en Rd la que venía después
Memoria
18
3.1.2 Formato de instrucciones
Como se especifica en el anterior capítulo, existen 6 formatos distintos de instrucción, mostrados en la
siguiente figura.
Figura 3.1-1 Formato de las instrucciones del conjunto RV32I.
En función del uso que hace una instrucción de los registros fuente, los valores inmediatos y el registro
destino, pertenece a un tipo de formato de instrucción. Cada uno codifica sus bits para un propósito
(indicar instrucción, direcciones o valores) pero trata de mantener los elementos comunes en los
mismos espacios para facilitar su decodificación.
En el caso de las variaciones B y J, no se especifica el valor de bit de menos peso del inmediato ya que
siempre será cero, así que se cambia su orden con el objetivo de simplificar la decodificación. El bit de
más peso del inmediato se encuentra en el bit de más peso de la instrucción ya que este se debe
extender por su signo, y así coincide su posición con el resto de formatos. También se trata de hacer
coincidir la mayor cantidad de posiciones de bits con el formato base del que proceden.
3.1.3 Disposición de la memoria
Aunque en RV32I trabajamos con un ancho de datos de 32 bits, la memoria de datos está preparada
para poder trabajar con bytes y medias palabras (8 y 16 bits, respectivamente). La memoria de
instrucciones solo necesita un tamaño de 32 bits puesto que el tamaño de las instrucciones es de una
palabra, pero, tal y como se indica en las especificaciones, sigue el mismo formato que la memoria de
datos. Es por eso que cada instrucción está separada por 4 direcciones, cada una albergando datos de
1 byte.
Diseño, implementación y test de un core RISC-V
19
Figura 3.1-2 Direccionamiento de la memoria.
El formato de almacenamiento en RISC-V es de tipo little-endian, lo que implica que el byte menos
significativo de una palabra se almacena en la dirección menos significativa de la memoria.
Para poder trabajar con estas especificaciones, tanto la memoria RAM de datos como la memoria ROM
de instrucciones se organiza en 4 bloques de Ax8 (siendo A la cantidad de direcciones que se usarán),
con los cuales se hará un bus de datos para tener una salida de 32 bits.
Figura 3.1-3 Bloque de memoria RAM.
De esta manera, la memoria de menos peso guarda las direcciones 0, 4, 8 y 12; la siguiente 1, 5, 9 y 13;
la tercera 2, 6, 10 y 14 y la de más peso 3, 7, 11 y 15. En el capítulo del diseño de la memoria se especifica
como se accede a un byte, una media palabra y una palabra en esta construcción (véase capítulo
3.2.14).
Memoria
20
3.2 Datapath
A continuación, se muestra paso a paso el diseño del microprocesador capaz de llevar a cabo todas las
instrucciones del set RV32I. La microarquitectura es de tipo multi ciclo, lo que significa que las
instrucciones se ejecutan en varios ciclos de reloj y no más de una a la vez. Todas las figuras han sido
creadas utilizando el programa “AutoCAD”.
3.2.1 PC y ROM
Figura 3.2-1 Registro “Contador de programa” y memoria ROM de instrucciones.
Los dos elementos básicos en el diseño de una microarquitectura son el contador de programa y la
memoria de instrucciones. El contador de programa guarda la dirección donde se encuentra la
instrucción a ejecutar.
Diseño, implementación y test de un core RISC-V
21
La memoria de instrucciones es una ROM (tan solo es de lectura) de bloque, por lo que la lectura se
hace de manera síncrona. Gracias a esto, no es necesario añadir un registro para guardar la instrucción,
ya que cuando cambie el contador de programa, la lectura de la instrucción se hará en un siguiente
ciclo donde ya no se precisa de la anterior instrucción, de manera que la unidad de control se ha
ajustado para cumplir este requisito. La ROM usada en este caso tiene una entrada de dirección con
un ancho de 9 bits, por lo que tiene un total de 512 direcciones, lo que permite un máximo de 128
palabras.
El primer paso, por lo tanto, es acceder a la dirección que guarda el contador de programa y leer la
instrucción a ejecutar de la memoria de instrucciones.
3.2.2 Decodificador de instrucciones
Figura 3.2-2 Bloque decodificador de instrucciones.
El decodificador de instrucciones es un bloque combinacional que separa y ordena el vector Instr para
poder extraer los datos necesarios para cada instrucción, tal y como se ve en las especificaciones de
RISC-V (véase capítulo 3.1.2).
Para seguir el formato de la ISA, la idea es tratar de simplificar la decodificación aprovechando el
formato que tienen las instrucciones. El hecho de que todos los inmediatos tengan el bit de más peso
Memoria
22
en la misma posición o que muchos elementos mantengan la misma posición reduce la complejidad
del esquema eléctrico del decodificador, el cual se muestra en la siguiente figura:
Figura 3.2-3 Esquema eléctrico del decodificador de instrucciones.
Aunque extraiga todos los elementos existentes, el resto del diseño tan solo trabajará con los
necesarios para cada tipo de instrucción gracias a la unidad de control, por lo tanto, todos los que no
se usa, los cuales tendrán un valor aleatorio ya que acceden a bits que tienen otro propósito, no
interrumpirán en el resto de elementos.
Diseño, implementación y test de un core RISC-V
23
3.2.3 Banco de registros
Figura 3.2-4 Banco de registros de RV32I.
El formato del registro está especificado en la ISA y se compone de 32 registros de un tamaño de 32
bits. El bloque funciona con escritura síncrona y lectura asíncrona y guarda datos de propósito general
con lo que poder operar directamente.
La lectura es dual (accede a dos registros distintos en un mismo ciclo de reloj), mientras que la escritura
es simple. Esta construcción es la óptima siguiendo las instrucciones de RV32I, puesto que cuando
operamos con registros debemos acceder a 2 simultáneamente mientras que tan solo es necesario
uno como destino para guardar resultados.
Los datos de registros de la instrucción, que son registro fuente 1 (rs1), registro fuente 2 (rs2) y registro
destino (rd) se conectan directamente a las entradas correspondientes de dirección del banco de
registros.
Memoria
24
3.2.4 Unidad aritmético-lógica
Figura 3.2-5 ALU y registros A y B.
Los datos de salida de los registros fuente se conectan a unidad aritmético-lógica (ALU) para poder
operar con ellos, aunque son guardados previamente en registros de 32 bits cuyo propósito es guardar
el dato durante un ciclo de reloj. Esto es debido a que la microarquitectura es multi ciclo y estos
registros separan dos estados que ocurren en distintos ciclos de reloj, la decodificación de la instrucción
y lectura de registros por un lado y la ejecución (operar con la ALU) por el otro.
La unidad aritmético-lógica es un elemento combinacional que ejecuta operaciones aritméticas y
lógicas entre los operandos A y B y saca el resultado por el puerto R. La entrada ALU_Control especifica
que operación se ejecuta.
Diseño, implementación y test de un core RISC-V
25
Tabla 3-4 Operaciones ejecutadas por la ALU (“u”: sin signo, “s”: con signo).
Operación Código Acción
add 0000 R = A + B sub 0001 R = A - B
sll 0010 R = A << B
slt 0100 R = 1 si A < B si no 0
sltu 0110 R = 1 si A <u B si no 0
xor 1000 R = A ⊕ B
srl 1010 R = A >> B
sra 1011 R = A >>s B
or 1100 R = A | B
and 1110 R = A & B
Para las operaciones de desplazamiento de bits (SLL, SRL, SRA) hay que tener en cuenta que el
operando B tiene 32 bits, pero teniendo en cuenta que el operando B también, un desplazamiento de
más de 32 bits es innecesario. Es por eso que en estas operaciones se extrae los 5 bits de menos peso
para que el rango de valores del operando B sea entre 0 y 31.
3.2.5 Operaciones con registros
Figura 3.2-6 Escritura del resultado de la ALU en el banco de registros.
Para poder operar con registros, y que de esta manera la microarquitectura pueda ejecutar
instrucciones de tipo R, es necesario conectar la salida de la ALU a la entrada de datos del registro
destino del banco de registros. El diseño de la figura 3.8 ya puede, así, extraer los datos de los registros
fuente, operar con ellos y guardar su resultado en el registro destino.
Memoria
26
3.2.6 Operaciones con inmediatos
Figura 3.2-7 Adición de inmediato de tipo I.
Las instrucciones de tipo I operan usando el registro fuente 1 como un operando y un inmediato
guardado en la misma instrucción como el otro. Se añade la salida I_imm que se genera en el
decodificador y se conecta al registro B usando un multiplexor para decidir si operar con el inmediato
o con el registro fuente 2. El resto de la operación coincide con las instrucciones de tipo R, por lo que
se usa el mismo esquema descrito previamente.
3.2.7 RAM
Figura 3.2-8 Memoria RAM de datos.
Diseño, implementación y test de un core RISC-V
27
La memoria de datos guarda valores a los que no se accede con tanta frecuencia como los del banco
de registros. La construcción es más sencilla y puede contener más direcciones, por lo que es ideal para
almacenar todo tipo de datos.
En la implementación mono ciclo, se accede a la memoria de instrucciones y la memoria de datos en
un mismo ciclo de reloj. En el caso multi ciclo no es necesario puesto que en un ciclo de reloj se extrae
la instrucción y en otro se ejecuta. Gracias a esto podemos simplificar la construcción añadiendo ambas
memorias en un mismo bloque donde comparten la misma entrada de dirección. De esta manera, de
las 128 palabras que puede almacenar la memoria, ya que el ancho de la entrada de dirección es de 9
bits, estas se repartirán entre ambas memorias.
La memoria RAM es de escritura y lectura síncrona, así que en el bloque se añade la entrada di para los
datos de entrada y Data para los de salida. Como se especifica en el capítulo 3.1.3, está formado por 4
bloques de 8 datos cada uno de manera que es compatible con las instrucciones SB y SH, las cuales
escriben datos de 1 o 2 bytes en la memoria RAM, extrayendo así tan solo un fragmento de la entrada
di de 4 bytes. La salida Data, en cambio, saca los 4 bytes de la dirección indicada y posteriormente se
modifica según lo requerido por la instrucción (véase capítulo 3.2.8).
El mapeado final de la memoria de datos e instrucciones se encuentra en el capítulo 3.2.14, donde
también se explica cómo se accede, como se escribe y como se lee cada tipo de memoria.
3.2.8 Carga de memoria
Figura 3.2-9 Lectura de datos de la memoria RAM.
Las instrucciones de acceso a la memoria, tanto carga como almacenamiento, se dividen en tres
tamaños: word (32 bits/4 bytes), half-word (16 bits/2 bytes) y byte (8 bits/1 byte).
Memoria
28
En el caso de las instrucciones de carga, se lee el dato de una dirección de la memoria RAM y se guarda
en el registro destino. Como el banco de registros solo es compatible con datos de 32 bits, estos se
deben extender. En función de que instrucción se esté ejecutando, se extienden sin signo o con signo.
Figura 3.2-10 Bloque encargado de adaptar el dato de la memoria.
Como se aprecia en la figura 3.12, se generan todas las adaptaciones posibles para las instrucciones de
RV32I y se hace uso de multiplexores para seleccionar la indicada.
Para indicar a que dirección de la memoria se quiere acceder, se suma el contenido del registro fuente
1 al inmediato tipo I. Una vez se obtiene la dirección, esta se debe conectar con el bloque de memoria.
Como ya usa esta entrada el PC, se añade un multiplexor para escoger a qué tipo de dirección
queremos acceder. Para ello separamos los 9 bits de menos peso del resultado de la ALU.
3.2.9 Almacenamiento en memoria
Figura 3.2-11 Escritura de datos en la memoria RAM.
Diseño, implementación y test de un core RISC-V
29
Como ocurre con las instrucciones de carga, en las instrucciones de almacenamiento sumamos el
contenido del registro fuente 1 con un inmediato para conseguir la dirección de la memoria RAM. En
ambos casos se permite acceder a memoria desalineada (por ejemplo, si se accede a la dirección 2 se
leerá los bytes de menos peso de la primera palabra y los 2 bytes de más peso de la siguiente palabra).
El inmediato para las instrucciones de almacenamiento es de tipo S. Cambia ya que se hace uso del
registro fuente 2 en vez del registro destino, pues en este caso hay que leer del banco de registros y no
escribir en este. Como ambos inmediatos se llevan siempre a la ALU, se añade un multiplexor a la salida
del decodificador para escoger con cual trabajar.
En cuanto a la escritura, se conecta la salida de datos del registro fuente 2 a la entrada de datos de la
memoria. Se añaden además las señales de control write enable y write lenght para permitir la escritura
y establecer que cantidad de bytes se quieren escribir. Como la RAM permite la escritura de tamaños
de datos de 1, 2 y 4 bytes, no es necesario adaptar la señal como se hace en las instrucciones de carga.
En el capítulo 3.2.14 se analiza la estructura interna de la memoria que permite este tipo de escritura.
3.2.10 Suma del PC
Figura 3.2-12 Adición de cableado que permite pasar a la siguiente instrucción.
La memoria de RISC-V está dividida en 4 bloques de 1 byte que forman las palabras de 4 bytes. Esto
implica que las instrucciones están separadas por 4 direcciones. Por lo tanto, para pasar a la siguiente
instrucción, se debe sumar 4 al valor actual de PC y escribirse nuevamente en este. Para reducir
hardware, se usa la ALU para llevar a cabo esta suma. Como la implementación es multi ciclo, se puede
utilizar la ALU para esta operación en ciclos de reloj de la instrucción donde se está haciendo uso de
otra parte del datapath (véase capítulo 3.3.1).
Para poder llevar los valores a sumar a los operandos de la ALU, se añade una constante de 32 bits de
valor 4 al multiplexor anterior al registro B y se añade otro a la entrada del registro A, donde se escoge
entre el valor del registro fuente 1 y el valor actual de PC.
Memoria
30
La entrada al registro PC prescinde de los 2 bits de menos peso del resultado de la ALU y les da un valor
fijo de 0. Como las direcciones de la memoria ROM son cada 4, estos dos bits siempre serán 0, ya que
se accede siempre de manera alineada. Aun así, se tiene en cuenta este procedimiento para evitar
acceso a direcciones desalineadas de la ROM en la instrucción JALR (véase capítulo 3.2.12).
3.2.11 Transferencia de control condicional
Figura 3.2-13 Adición del inmediato B y el estado zero para las instrucciones de transferencia de control condicional.
Las instrucciones de tipo B sirven para poder ejecutar código condicional como la sentencia if o el bucle
while. A diferencia de la memoria RAM, la memoria ROM no permite el acceso desalineado, pues las
instrucciones son de 32 bits y acceder solo a parte de estas genera que el decodificador extraiga valores
erróneos.
Como el set RV32I tiene una extensión para trabajar con instrucciones de 16 bits, las especificaciones
de la ISA tan solo ignoran el bit de menos peso. Esto se hace para evitar acceder a direcciones impares,
pues con instrucciones de 2 bytes, las instrucciones se encontrarían separadas por 2 direcciones. En
este caso no se hace uso de la extensión de 16 bits, por lo que, aunque se le pueda dar un valor de ‘1’
al segundo bit de menos peso del inmediato B, este siempre se interpretará siempre como un ‘0’. De
esta manera se evita el acceso a direcciones de la memoria ROM desalineadas. Como esta es la única
diferencia, se define el inmediato B como una variación del inmediato S.
El funcionamiento de este tipo de instrucción es el siguiente: se lee los valores del registro fuente 1 y
2 y se operan en la ALU, pero el resultado se ignora. Para este caso se ha añadido una señal de estado
zero, que se activa en caso de que el resultado sea igual a 0. La operación que se lleva a cabo y el valor
que debe tener zero para que se cumpla o no la condición depende de cada instrucción, pues esta
parte es trabajo de la unidad de control. En caso de que se cumpla la condición, se suma la dirección
actual a PC y se escribe nuevamente en este. En caso contrario, se accede a la siguiente instrucción,
sumando la constante de valor 4 en lugar del inmediato de tipo B.
Diseño, implementación y test de un core RISC-V
31
3.2.12 Transferencia de control incondicional
Figura 3.2-14 Adición del inmediato J para las instrucciones de transferencia de control incondicional.
Las instrucciones de transferencia de control incondicional hacen el salto en el PC directamente. Hay
dos tipos de instrucción:
JAL es una instrucción de salto “PC-relative”. Esto quiere decir que el valor del inmediato define la
separación entre la dirección de la instrucción actual y la dirección objetivo. Para ello, se usa el
inmediato J, una variación del inmediato U, que prescinde de los 2 bits de menos peso para que tengan
un valor fijo de 0, igual que en el inmediato B. Suma este inmediato al valor actual de PC y lo reescribe
para hacer el salto.
JALR es una instrucción de salto “absolute”. En este caso, el valor de la suma del inmediato y el valor
del registro fuente uno es directamente la dirección objetivo. Como para extraer esta dirección
objetivo se suma con el registro fuente, no es necesario que el inmediato tenga los 2 bits de menos
peso igualados a 0 para evitar acceder a direcciones desalineadas, pues posteriormente a la suma
podría volver a cambiar el valor de los 2 bits de menos peso. Es por este caso que, tal y como se ve en
el capítulo 3.2.10, se cambia los dos bits de menos peso por ceros en la entrada del registro PC.
Ambas instrucciones guardan previamente la dirección de la instrucción que precede a estas en el
registro destino.
Memoria
32
3.2.13 Carga de inmediato U
Figura 3.2-15 Carga de inmediato U.
Las instrucciones de formato U contienen un inmediato de 20 bits, a los cuales se añade 12 ceros a su
derecha para formar el inmediato final de 32 bits. La instrucción LUI (Load Upper Inmediate) guarda
directamente este valor en el registro destino. Es por eso que se cablea directamente la salida del
inmediato U al registro destino. Como este comportamiento es diferente al del resto de inmediatos,
se separa del multiplexor que elige el resto.
La instrucción AUIPC (Add Upper inmediate to PC) suma el inmediato U de 32 bits al valor actual de PC
y lo guarda en el registro destino. Para hacer esta suma, se debe llevar el inmediato al registro B. Se
añade por esto al multiplexor a la entrada del registro.
3.2.14 E/S mapeado por memoria
Figura 3.2-16 Mapeado de la memoria de datos e instrucciones.
Diseño, implementación y test de un core RISC-V
33
En la figura 3.17 se observa el mapeado interno de la memoria que contiene la memoria ROM de
instrucciones, la memoria RAM de datos, el registro del dispositivo de entrada y el registro del
dispositivo de salida.
El set de instrucciones de RV32I no tiene instrucciones específicas para comunicarse con dispositivos
externos, por lo que la comunicación con puertos de entrada y salida del datapath se hace con
mapeado por memoria. Esto quiere decir que se añaden los registros donde se guardan los valores
introducidos o extraídos por componentes externos y se usan las instrucciones de load y store para
comunicarse con estos. Para un datapath genérico se harían ambos de 32 bits, pues es el ancho de
datos con el que se trabaja. En este caso se ha querido implementar directamente para la placa donde
se implementa el diseño, la Nexys 2 (véase Implementación del diseño), por lo que el registro del
dispositivo de entrada es de 8 bits y el de salida de 16 bits.
A la izquierda se aprecia el bloque codificador de la dirección donde, a partir de esta y de la señal de
control write lenght, se generan las señales enable de cada registro y memoria.
La disposición de la memoria es la siguiente:
Figura 3.2-17 Disposición de la memoria.
A la derecha se indica el valor que debe tener la dirección para referirse a cada bloque, lo cual se
codifica para obtener tanto el enable de los registros como para la señal de selección del multiplexor
de salida.
Memoria
34
El dispositivo de entrada tiene un ancho de 1 byte, por lo que tan solo guarda una dirección en la
memoria, la 0x100. Si se accede a esta dirección para una instrucción load, el multiplexor de salida saca
este valor por la salida de data, el cual es previamente extendido a 32 bits sin signo. Al ser un registro
de entrada es tan solo de lectura. De la escritura se encarga el componente externo.
El registro del dispositivo de salida es de escritura y lectura. La lectura al componente externo se hace
de manera directa, y la instrucción load se usa por si se quiere operar con el valor de este registro. En
este caso, es de 2 bytes, por lo que hay 2 registros de 8 bits con las direcciones 0x102 y 0x103. Ambas
direcciones comparten los 8 bits de más peso “10000001”, por lo que si se accede a esta dirección se
activarán CS_OUT, la señal de enable de estos registros. El último bit se usa para escoger entre uno de
los 2 registros, activando CS_OUT0 o CS_OUT1 en función de cuál de los 2 registros se deba usar, útil
para la instrucción SB, ya que se puede escribir tan solo 1 byte. El comportamiento del codificador
empleado para el registro de salida en función de si se accede a este (CS_OUT), de que tamaño es el
dato de entrada (MWLe) y el bit de menos peso de la dirección (dir(0)) será el siguiente:
Tabla 3-5 Comportamiento del codificador usado para la señal enable del registro del dispositivo de salida.
Activación
CS_OUT MWLe dir(0) CS_OUT0 CS_OUT1
0 X X 0 0
1 00 0 1 0
1 00 1 0 1
1 01 0 1 1
1 01 1 0 0
1 11 0 0 0
1 11 1 0 0
La señal CS_OUT se activa si dir(8:1) es igual a “10000001”, ya que entonces estamos accediendo a al
dispositivo de salida. Si se quiere escribir 1 byte, se escoge si en el byte de menos o más peso en función
del bit de menos peso de la dirección. Si se escribe 2 bytes en la dirección 0x102, se escribirá en ambos
registros. Todos los demás casos resultan en no escribir, ya que hay una cantidad de datos que se
sobrepasa y queda excluido.
La memoria RAM ocupa de la dirección 0x080 a la dirección 0x0FF. Todas comparten los mismos 2 bits
de más peso “01” por lo que, si la dirección contiene estos 2 bits, se activará la señal CS_RAM, que
permite la escritura o lectura de esta memoria. Como se explica en el capítulo 3.1.3, la memoria RAM
esta divida en 4 bloques de 32x8, los cuales tienen un bus de datos a la salida y generan una memoria
de 32x32 compatible con datos de 1, 2 y 4 bytes. Una vez se accede a la RAM hay que tener en cuenta
a que dirección exacta de estas se accede, observando los dos bits de menos peso (dir(1:0)) y la señal
MWLe en el caso de escribir (para la lectura siempre se lee la dirección escogida y las 3 siguientes).
Diseño, implementación y test de un core RISC-V
35
El valor dir(6:2) define a que palabra alineada se está accediendo y dir(1:0) a cuál de los 4 registros. La
función del codificador para la RAM será el siguiente:
Tabla 3-6 Comportamiento del codificador usado para las señales enable de la memoria RAM.
Activación
CS_RAM MWLe dir(1:0) CS_RAM0 RAM0_1 CS_RAM1 RAM1_1 CS_RAM2 RAM2_1 CS_RAM3
0 XX XX 0 0 0 0 0 0 0
1 00 00 1 0 0 0 0 0 0
1 00 01 0 0 1 0 0 0 0
1 00 10 0 0 0 0 1 0 0
1 00 11 0 0 0 0 0 0 1
1 01 00 1 0 1 0 0 0 0
1 01 01 0 0 1 0 1 0 0
1 01 10 0 0 0 0 1 0 1
1 01 11 1 1 0 0 0 0 1
1 10 00 1 0 1 0 1 0 1
1 10 01 1 1 1 0 1 0 1
1 10 10 1 1 1 1 1 0 1
1 10 11 1 1 1 1 1 1 1
La señal CS_RAM se activa cuando dir(8:7) es igual a “01”, pues en este caso estamos accediendo a la
memoria RAM. En función de write enable y la dirección exacta a la que estamos accediendo (dir(1:0))
activaremos las RAM específicas, usando las señales CS_RAM*. Como se permite el acceso desalineado
a la memoria, hay casos donde algunas memorias deben acceder a una posición superior a la
especificada (por ejemplo, si se escribe media palabra en la dirección 0x082, la dirección a la que se
accederá en las memorias (dir(6:2)) será “00000”, la primera, pero la dirección exacta (dir(1:0)) es “11”,
la RAM3, por lo tanto, la siguiente dirección es el valor de la dirección “00001” de la RAM0). Para ello
se genera las señales RAM*_1 que sirven para escoger la dirección exacta o esta sumada a una
constante 1.
La memoria ROM es tan solo de lectura y no se permite el acceso desalineado, por lo que si se accede
a dir(8:7) “00”, se activarán las 4 ROM y se leerá el valor de la dirección especificada.
Tanto la memoria RAM como la ROM son de lectura síncrona (y la RAM además de escritura síncrona),
por lo que las señales CS establecen cuando se pueden hacer ambas operaciones. Para la escritura de
la RAM, además de deber referirse a su dirección, se debe activar la señal write enable. Lo mismo
ocurre con el registro del dispositivo de salida.
Las instrucciones de la ROM toman un camino diferente a los datos de la RAM y los registros de los
dispositivos de entrada y salida, por lo que cada uno tiene su salida específica.
Memoria
36
Para el caso de los datos, se escoge con un multiplexor cuya señal de selección se codifica de manera
síncrona a partir de las señales CS de cada memoria y registro. Este codificado es síncrono ya que como
la lectura también lo es así se sincroniza el instante de selección y lectura.
3.2.15 E/S Nexys 2
Figura 3.2-18 Incorporación de los registros E/S mapeados por memoria con los componentes de la placa Nexys 2.
Para poder comunicarse con la placa con la que se implementará el diseño (véase Implementación del
diseño) se ha añadido el puerto de entrada del dispositivo de entrada SW y la adaptación y puerto de
salida del monitor de siete segmentos, salidas SSEG y AN.
El dispositivo de entrada SW se conforma de 8 interruptores en línea haciendo un bus de 8 bits, como
se muestra en la siguiente figura de manual de referencia de la placa:
Diseño, implementación y test de un core RISC-V
37
Figura 3.2-19 Disposición de los interruptores de la placa Nexys 2. (14)
El monitor de siete segmentos se forma de 4 bloques de 8 leds que representan números decimales y
además se usa para las letras hexadecimales. Como hay un total de 32 leds, el tamaño del puerto de
salida sería demasiado grande, por lo que su funcionamiento se separa en dos señales distintas:
La señal SSEG es un vector de 8 bits activo a nivel bajo que escoge que leds se encienden. Cada led se
corresponde a una letra, de la “A” a la “G” y el punto decimal se representa con “DP”. El led “A” se
asocia al bit de más peso del vector SSEG y el resto siguen de forma alfabética, siendo “DP” el bit de
menos peso.
La señal AN selecciona cuales de los 4 bloques se enciende con un vector de 4 bits activo a nivel bajo,
siendo el bloque de la izquierda encendido por el bit de más peso y el de la derecha por el de menos
peso.
Figura 3.2-20 Funcionamiento del monitor de siete segmentos de la placa Nexys 2. (14)
Memoria
38
Como se puede mostrar los números decimales y las letras A-F, se puede usar este monitor para
representar un vector de 2 bytes en hexadecimal. Para ello se debe sincronizar que dígito del vector se
muestra y cuál de los cuatro bloques del monitor de siete segmentos se enciende.
Figura 3.2-21 Cronograma de sincronización de las señales SSEG y AN. (14)
Como indica la figura 3.22, el periodo de refresco debe comprenderse entre 1 y 16 ms, de esta manera
a la vista del ojo humano, se verá a la vez cada número en su bloque del monitor correspondiente. Para
esto se hace un prescalado de la señal de 50 MHz del reloj de la placa con un contador de modulo
49999. De esta manera, cada vez que se reinicia este contador significa que ha pasado 1 ms. Este
instante se usa como señal de activación de un contador de modulo 4 del cual sale la señal con la que
se sincroniza las salidas SSEG y AN:
Tabla 3-7 Esquema de sincronización de las señales SSEG y AN.
Valor del contador de módulo 4 AN SSEG
00 1110 Dígito 0
01 1101 Dígito 1
10 1011 Dígito 2
11 0111 Dígito 3
Diseño, implementación y test de un core RISC-V
39
Toda esta adaptación para la conexión con el monitor de siete segmentos se encuentra descrita en el
datapath.
Figura 3.2-22 Adaptación de los puertos E/S a la placa Nexys 2.
3.2.16 Datapath Completo
Figura 3.2-23 Esquema completo del datapath.
En la figura 3.24 se muestra el diagrama de bloques completo del datapath diseñado para llevar a cabo
todas las instrucciones del set de instrucciones RV32I sin privilegio, además de la adaptación y conexión
con la placa Nexys 2, la cual se usará posteriormente para implementar dicho diseño (véase Anexo A.
Datapath)
Memoria
40
3.3 Unidad de control
La unidad de control es una máquina de estados finito encargada de establecer valores a las señales
de control del datapath en función del estado en el que se encuentra y de las señales de estado
recibidas, las cuales definen que instrucción se debe ejecutar, en el caso de Funct7, Funct3 y Opcode
(véase Anexo D. Codificación instrucciones RV32I) o si el resultado de una operación es igual a 0, en el
caso de FZ. Todas las figuras han sido creadas utilizando el programa “Inkscape”.
Tabla 3-8 Función de las señales de control y estado.
Señales de control Señales de estado
Señal Función Señal Función
EPC Escribir en el contador de programa Funct7 Código de instrucción de 7 bits
I_D Llevar instrucción o datos a la
memoria Funct3 Código de instrucción de 3 bits
EMW Escribir en memoria Opcode Código de operación
MWLe Tamaño de escritura en memoria FZ Flag de 0
imm_ISBJ Sacar inmediato tipo I, B, S o J
D_sign Escoger si el dato leído de memoria se
extiendo con o sin signo
D_Lenght Escoger tamaño lectura de la memoria
di_sel Escoger dato de entrada en el banco
de registros
ERWr Escribir en el banco de registros
A_sel Escoger que entra en el registro A
B_sel Escoger que entra en el registro B
EAR Escribir en el registro A
EBR Escribir en el registro B
ALUControl Seleccionar que operación hacer en la
ALU
Cada estado representa el valor que tendrán las señales de control para cada ciclo de reloj y la
secuencia que seguirá ira conforme a la instrucción que se está ejecutando (véase Anexo C. Tabla
señales de control de UC).
A continuación, se explica paso a paso como se construye la máquina de estados finita para que el
diseño pueda ejecutar todas las instrucciones.
Diseño, implementación y test de un core RISC-V
41
3.3.1 Fetch
Figura 3.3-1 Diagrama de estados de la operación fetch.
Al inicio del programa y de todas las instrucciones, estos 2 estados son el primer paso que se ejecuta.
Por un lado, el estado 0 lee la instrucción de la dirección de la memoria ROM guardada en PC y, por el
otro lado, el estado 1 añade el valor de PC y la constante 4 en los registros de los operandos de la ALU
para posteriormente sumarlos.
Aunque estas 2 acciones se pueden ejecutar simultáneamente, se separan ya que, al usarse la ALU para
hacer la suma del PC, depende de la instrucción que se ejecute se podrá guardar el resultado
nuevamente y extraer la siguiente instrucción en un diferente estado, el más idóneo para cada ocasión.
Es una forma poco esquemática de hacer el fetch, pero reduce el tiempo en el que tardan las
instrucciones en ejecutarse. Además, necesitamos esperar un ciclo de reloj extra para leer el valor de
Opcode y decidir a qué estado moverse en función de la instrucción, ya que la lectura de la ROM es
síncrona. Si la lectura de la ROM fuese asíncrona reduciríamos el tiempo de varias instrucciones, pero
eso implicaría implementar una ROM distribuida y no de bloque, lo que a la hora de la implementación
empeora los resultados de síntesis del diseño (véase capítulo 5.1.1)
Tabla 3-9 Valor de las señales de control para los estados S0 y S11.
S0 S1
fetch Operandos
PC + 4
I_D 0 -
A_sel - 0
B_sel -- 00
EAR 0 1
EBR 0 1
1 Todas las tablas del valor de las señales de control para los distintos estados muestran solo las señales relevantes. El resto tendrán siempre el valor de 0 para las de activación y la de indiferente (“-“) para las de selección.
Memoria
42
En S0 se selecciona la entrada 0 del multiplexor I_D para que el valor de PC vaya a la entrada de
dirección de la memoria. Al ser una dirección de la ROM, está se activará y en el siguiente ciclo de reloj
se leerá la instrucción que contiene. Para S1, se seleccionan los puertos de la constante 4 y la salida de
PC en los multiplexores de los registros de los operandos de la ALU y se activan para guardar su valor.
3.3.2 Operaciones entre registros
Figura 3.3-2 Diagrama de estados de las operaciones entre registros.
Para realizar las operaciones entre registros se divide la ejecución de la instrucción en tres fases: fetch,
selección de operandos y operación.
La selección de operando se hace siempre en S2, donde el valor de los registros fuentes se guarda en
los registros de los operandos de la ALU, y la operación en los estados del S3 al S12, siendo cada uno el
que completa la operación de cada instrucción.
El fetch se encuentra repartido en tres fases: la lectura de la instrucción, la selección de operandos para
sumar PC y la escritura en PC.
La lectura de la instrucción se encuentra tanto en S0 como en los estados del S3 al S12. Esto es debido
a que, en los estados de la operación, la memoria queda libre, por lo que se puede hacer esta lectura
simultáneamente. Gracias a eso, tras estos estados se puede ir directamente a S1 sin pasar por S0,
ahorrando tiempo de ejecución. En caso de que sea el inicio del programa o que se venga de una
instrucción distinta, se pasará por S0 para poder leer la instrucción antes de ejecutarla.
La selección de operandos se encuentra siempre en S1 y la escritura en PC se hace en S2
simultáneamente a la selección de los operandos de la instrucción.
Diseño, implementación y test de un core RISC-V
43
Tabla 3-10 Valor de las señales de control para los estados del S2 al S12.
S2 S3 S4 S5 S6 S7 S8 S9 S10 S11 S12
Operandos
R ALU add
ALU sub
ALU sll
ALU slt
ALU sltu
ALU xor
ALU srl
ALU sra
ALU or
ALU and
EPC 1 0 0 0 0 0 0 0 0 0 0
I_D - 0 0 0 0 0 0 0 0 0 0
di_sel -- 10 10 10 10 10 10 10 10 10 10
ERWr 0 1 1 1 1 1 1 1 1 1 1
A_sel 1 - - - - - - - - - -
B_sel 11 - - - - - - - - - -
EAR 1 0 0 0 0 0 0 0 0 0 0
EBR 1 0 0 0 0 0 0 0 0 0 0
ALUControl 0000 0000 0001 0010 0100 0110 1000 1010 1011 1100 1110
La señal EPC activa la escritura en el registro PC para guardar la suma PC + 4 que se ejecuta en el mismo
estado. Para el resto de estados, la señal I_D se mantiene a 0 para leer la siguiente instrucción en la
ROM. Con las señales di_sel a “10” y ERWr a ‘1’, se guarda el resultado de la ALU en el registro destino
del banco de registros. En cada operación, se introduce el valor de ALUControl correspondiente.
3.3.3 Operaciones con inmediatos
Figura 3.3-3 Diagrama de estados de las operaciones con inmediatos.
Las operaciones con inmediatos se ejecutan igual que las operaciones entre registros, cambiando el
registro fuente por el inmediato de tipo I. S13 es un estado equivalente a S2 donde se cambia que valor
se escribe en el registro B.
Memoria
44
Tabla 3-11 Valor de las señales de control para el estado S13.
S13
Operandos I
EPC 1
ISBJ_sel 00
A_sel 1
B_sel 10
EAR 1
EBR 1
ALUControl 0000
El registro A se mantiene igual que en S2, pero el multiplexor del registro B ahora selecciona la entrada
de los inmediatos, de los cuales se selecciona el de tipo I con la señal ISBJ_sel a “00”.
3.3.4 Carga de memoria
Figura 3.3-4 Diagrama de estados de la carga de memoria.
La carga de memoria se divide en 4 fases: fetch, selección de operandos, lectura de memoria y escritura
en el banco de registros.
El fetch se organiza igual que en las operaciones de formato R e I. La selección de operandos sirve para
recoger los valores que se sumaran para obtener la dirección objetivo de la memoria RAM. Una vez
obtenidos, al introducir esta dirección en la memoria se activará la lectura de la RAM y en el siguiente
ciclo de reloj el contenido saldrá por Data.
La última fase tiene un estado diferente en función de la instrucción que ejecutemos. En todos ellos se
escribe en el registro destino, pero la adaptación de la señal es diferente en cada caso.
Diseño, implementación y test de un core RISC-V
45
Tabla 3-12 Valor de las señales de control para los estados del S25 al S30.
S25 S26 S27 S28 S29 S30
Lectura Mem
Cargar byte
Cargar media
palabra
Cargar palabra
Cargar byte sin
signo
Cargar media
palabra sin
signo
I_D 1 0 0 0 0 0
D_sign - 0 0 - 1 1
D_Lenght -- 10 01 00 10 01
di_sel -- 01 01 01 01 01
ERWr 0 1 1 1 1 1
ALUControl 0000 ---- ---- ---- ---- ----
Como se explica en el capítulo 3.2.8, se generan todas las adaptaciones disponibles y se usan
multiplexores para seleccionar la señal requerida por la instrucción. La señal D_sign escoge entre la
extensión con o sin signo de 1 o 2 bytes y la señal D_Lenght escoge entre la selección de la memoria
de 1, 2 o 4 bytes.
3.3.5 Almacenamiento en memoria
Figura 3.3-5 Diagrama de estados del almacenamiento en memoria.
El almacenamiento en memoria se divide en 3 fases, una menos que la carga, pero la última instrucción
hace uso de la memoria, ya que es la escritura en esta, por lo que no se puede extraer simultáneamente
la instrucción. Por lo tanto, al finalizar la instrucción, se pasa al estado S0 donde se inicia el fetch.
En S31 se decodifican y guardan los operandos para sumar en la ALU y en los estados S32 a S34 se
escribe en la dirección que resulta de la operación el valor del registro fuente 2. En este caso, no es
necesario adaptar la señal, sino que tan solo se reescribe la cantidad de bits que marca la instrucción.
Memoria
46
Tabla 3-13 Valor de las señales de control para los estados del S31 al S34.
S31 S32 S33 S34
Operandos
S Almacenar
byte
Almacenar media
palabra
Almacenar palabra
EPC 1 0 0 0
I_D - 1 1 1
EMW 0 1 1 1
MWLe -- 00 01 10
A_sel 1 - - -
B_sel 10 -- -- --
EAR 1 0 0 0
EBR 1 0 0 0
ALUControl 0000 0000 0000 0000
La señal I_D se mantiene a 1 en el estado de escritura para introducir la dirección de la RAM obtenida
de sumar rs1 y el inmediato S. Con EMW activado se permite hacer la escritura y MWLe selecciona que
cantidad de bytes se reescriben.
3.3.6 Transferencia de control condicional
Figura 3.3-6 Diagrama de estados de transferencia de control condicional.
Diseño, implementación y test de un core RISC-V
47
La transferencia de control condicional es el tipo de instrucción más largo ya que el S1 se desperdicia.
Como se debe esperar un ciclo de reloj para leer la instrucción no se puede prescindir de este estado,
pero como estas instrucciones pueden resultar en un salto de PC, los operando de la ALU se reescriben
en S19 sin ser usados. Además, en el último estado se actualiza el valor de PC, por lo que no se puede
leer la siguiente instrucción en el mismo ciclo, volviendo así al S0 al final de la instrucción.
Para llevar a cabo estas instrucciones se sigue el siguiente procedimiento: se decodifican y guardan los
valores de los registros fuente en los registros de los operandos de la ALU, se hace la operación indicada
y en función de la instrucción y del resultado de la señal de estado FZ se añade al operando B el
inmediato de tipo B o la constante 4. Para finalizar se escribe el resultado de la suma de los operandos
A y B en PC.
Tabla 3-14 Esquema de operación y resultado de la transferencia de control condicional.
INSTRUCCIONES DE TRANSFERENCIA DE CONTROL
Instrucción Operación Operación Si FZ = '1' Si FZ = '0'
BEQ Saltar si son iguales A XOR B Salta No salta
BNE Saltar si no son
iguales A XOR B No salta Salta
BLT Saltar si es menor
que A SLT B No salta Salta
BGE Saltar si es mayor o
igual que A SLT B Salta No salta
BLTU Saltar si es menor
que sin signo A SLTU B No salta Salta
BGEU Saltar si es mayor o igual que sin signo
A SLTU B No salta Salta
En el caso de que se cumpla la condición y la instrucción “salte”, se suma el valor actual de PC al
inmediato de tipo B, por lo tanto, se dice que es un salto “PC-Relative” ya que el inmediato define la
diferencia entre la dirección de la instrucción actual y la dirección objetivo.
Memoria
48
Tabla 3-15 Valor de las señales de control para los estados del S18 al S24.
S18 S19 S20 S21 S22 S23 S24
Salto Operandos
B Comparación
igual que
Comparación mayor o menor
Comparación sin signo
Cumple (salto)
No cumple (fetch)
EPC 1 0 0 0 0 0 0
ISBJ_sel -- -- -- -- -- 10 --
A_sel - 1 - - - 0 0
B_sel -- 11 -- -- -- 10 00
EAR 0 1 0 0 0 1 1
EBR 0 1 0 0 0 1 1
ALUControl 0000 ---- 1000 0100 0110 ---- ----
En S19 se recogen los operandos con los que hacer la operación, el contenido de ambos registros
fuente. Para cada instrucción hay un estado diferente cada uno con una operación en la ALU distinta.
No se utilizan los estados S6, S7 y S8 porque, aunque se haga la misma operación, el resultado no se
guarda en el banco de registros.
S23 es el estado para cuando se cumple la condición y guarda en el registro B el inmediato de tipo B.
En el caso de S24 guarda la constante 4. Para finalizar, S18 suma el contenido de los registros A y B y lo
escribe en PC.
3.3.7 Transferencia de control incondicional
Figura 3.3-7 Diagrama de estados de transferencia de control incondicional.
Diseño, implementación y test de un core RISC-V
49
Las instrucciones de transferencia de control incondicional se dividen en 3 fases: fetch, decodificación
de operandos y salto.
Igual que en las instrucciones de transferencia de control condicional, el último estado es actualizar el
valor de PC, por lo que tras acabar vuelve a S0 para leer la siguiente instrucción de la memoria.
En la fase de decodificación aprovecha que en S1 se ha suma el valor 4 al PC para guardar la dirección
de la siguiente dirección tal y como indica las especificaciones de RISC-V. Simultáneamente, decodifica
y escribe en los registros de los operandos los valores necesarios para el salto. En el caso de la
instrucción JAL (S16), guarda el inmediato tipo J en el registro B y no cambia el A para que continúe el
valor de PC que se ha guardado en S1. Para la instrucción JALR (S17), guarda el inmediato tipo I y el
valor del registro fuente 1.
Tabla 3-16 Valor de las señales de control para los estados S16 y S17.
S16 S17
Operandos
J Operandos
JALR
ISBJ_sel 11 --
di_sel 10 10
ERWr 1 1
A_sel - 1
B_sel 10 10
EAR 0 1
EBR 1 1
ALUControl 0000 0000
En ambas instrucciones se hace la suma PC + 4 y a la vez se guardan los operandos necesarios en los
registros A y B, tal y como se ha indicado.
Memoria
50
3.3.8 Carga de inmediato U
Figura 3.3-8 Diagrama de estados de carga de inmediato U.
Para las instrucciones de formato U tan solo hay que extraer el inmediato y guardarlo directamente u
operando previamente. En la instrucción LUI (S14) tan solo hay que guardar el inmediato de tipo U, el
cual ya se adapta en la misma decodificación. En AUIPC (S15) se debe sumar a PC y posteriormente
guardar en el banco de registros, por lo que, aprovechando que el registro A ya contiene el valor de
PC, se escribe el inmediato U en el registro B, se suman y se guarda en el registro destino.
Como LUI tan solo necesita un ciclo de reloj, en el cual actualiza el valor de PC, debe volver a S0 para
leer la nueva instrucción de la ROM. En el caso de AUIPC, se usa el estado de la suma entre registros o
con inmediatos (formato R e I), el cual ya extrae la instrucción mientras se guarda el resultado en el
banco de registros.
Diseño, implementación y test de un core RISC-V
51
4. Simulación del diseño
Para simular y así verificar el funcionamiento del diseño creado, se ha usado el programa ModelSim,
con el que también se ha creado la descripción VHDL (véase Anexo B. Descripción VHDL) que se
necesita tanto para la simulación como para la implementación.
4.1 Testbench y valores iniciales
Una vez todos los códigos del datapath, el código de la unidad de control y la descripción nombrada
“TOP” que los une se crean, se debe generar el testbench y dar valores iniciales a ciertos registros y
memorias.
El testbench, o banco de pruebas, es una descripción usada para simular una descripción VHDL. Para
ello, se crea una entidad vacía y dentro de la arquitectura se describe la unidad bajo test (UUT), donde
se aprovecha para especificar que valores tomarán las entradas en un lapso de tiempo. De esta manera
se verifica el valor de las memorias, los registros y las salidas.
En el texto de abajo se muestra como esta descrito en VHDL el comportamiento que se le han dado a
las entradas para la simulación, teniendo en cuenta que “CLK_PERIOD” es una constante de 20 ns. Se
ha tomado este periodo ya que coincide con la frecuencia de la placa usada en la implementación.
Además, la señal de reloj empieza con un valor inicial de ‘0’ (de esta manera en el instante inicial haya
un flanco de subida) y la señal reset a ‘1’.
27 CLK_PROCESS : process
28 begin
29 CLK_FPGA <= '1';
30 wait for CLK_PERIOD/2;
31 CLK_FPGA <= '0';
32 wait for CLK_PERIOD/2;
33 end process;
34
35 RST_PROCESS : process
36 begin
37 wait for 5 ns;
38 RST_FPGA <= '0';
39 wait;
40 end process;
Memoria
52
En cuanto a los registros y las memorias, sus valores iniciales se colocan en la declaración de su propia
señal en el código que lo incluye. En cuanto a la memoria ROM, esta contiene las instrucciones a
ejecutar, por lo que sus valores iniciales se especifican en cada una de las simulaciones. Debe
recordarse que la memoria está dividida en 4 bloques formando un bus de datos, por lo que cada
palabra debe separarse en una ROM distinta cuando se describe.
Se ha optado por dar unos valores previos a ciertas direcciones de la memoria RAM y a todos los
registros del banco de registros para facilitar las simulaciones. Se puede observar dichos valores en las
siguientes tablas:
Tabla 4-1 Valor inicial de las direcciones de la memoria RAM indicadas.
DIR_MEM Valor
0 AA
1 BB
2 CC
3 DD
Tabla 4-2 Valor inicial del banco de registros.
Registro Valor Hx Valor Dec Registro Valor Hx Valor Dec
x0 00000000 0 x16 FFFFFF10 -240
x1 00000001 1 x17 00000011 17
x2 00000002 2 x18 00000012 18
x3 00000003 3 x19 00000013 19
x4 FFFFFFFC -4 x20 FE000000 -225
x5 00000005 5 x21 00000015 21
x6 00000006 6 x22
Registros destinados a guardar el resultado de las operaciones.
x7 00000007 7 x23
x8 0000005D 97 x24
x9 E718FFAA -417792086 x25
x10 0000001 1 x26
x11 0000000A 10 x27
x12 0000000C 12 x28
x13 000000BD 189 x29
x14 0000ABCD 43981 x30
x15 0000000F 15 x31
Diseño, implementación y test de un core RISC-V
53
4.2 Instrucciones de formato R
Para verificar que funciona la suma del contador de programa y para unir varias instrucciones en una
sola simulación, consiguiendo reducir espacio y mejorar la comprensión, se ha organizado las secciones
por formato de instrucción, puesto que sus funciones son similares según el formato y que hacen uso
de los mismos componentes del diseño.
Para llevar a cabo las simulaciones, se debe convertir el programa en código ensamblaje a código
máquina. Como se observa en los cronogramas, se ha usado hexadecimal para describir y mostrar las
instrucciones.
4.2.1 Explicación del programa
El programa a ejecutar es el siguiente:
Tabla 4-3 Programa usado para simular las instrucciones de formato R.2
Ensamblador Máquina (rs1) (rs2) Operación Resultado
ADD x22,x2,x3 00310B33 210 310 210 + 310 510
SUB x23,x2,x3 40310BB3 210 -310 210 – 310 -110
SLL x24,x15,x1 00179C33 1510 110 1510 x 2110 3010
SLT x25,x4,x3 00322CB3 -410 310 -410 < 310 110
SLTU x26,x4,x3 00323D33 -410 310 410 < 310 010
XOR x27,x12,x11 00B64DB3 1210 1010 11002 xor 10102 01102 = 610
SRL x28,x12,x2 006A5E33 1210 210 1210 / 2210 310
SRA x29,x20,x6 406A5EB3 -22510 610 -225
10 / 2610 -219
10 = -52428810
OR x30,x12,x11 00B66F33 1210 1010 11002 or 10102 11102 = 1410
AND x31, x12, x11 00B67FB3 1210 1010 11002 and 10102 10002 = 810
Teniendo en cuenta el formato que se usa para escribir el código ensamblaje de RISC-V (véase Tabla
3-1) y que la nomenclatura de los registros se compone de la “x” seguida del número de la dirección
en decimal, se determina que, en orden, aparecen el registro destino, el registro fuente 1 y el registro
fuente 2. Por lo tanto, teniendo en cuenta las direcciones del programa y el contenido que guarda cada
registro fuente, se muestran las operaciones que se están haciendo y el resultado esperado. En las
operaciones se usan operandos simples para facilitar su comprensión y en las instrucciones donde se
trabaja con complemento a 2 (SLT y SRA) se usan negativos para comprobar que el resultado es
correcto para números enteros.
2En las operaciones lógicas se muestran solo los bits que influyen en la operación teniendo en cuenta que el resto tiene valor ‘0’.
Memoria
54
4.2.2 Resultados de la simulación
Figura 4.2-1 Primera fila de resultados de la simulación de las instrucciones de formato R.
Diseño, implementación y test de un core RISC-V
55
Figura 4.2-2 Segunda fila de resultados de la simulación de las instrucciones de formato R.
En estas figuras se observa el cronograma de la simulación usando el software ModelSim. Para facilitar
la comprensión, se ha marcado con líneas cada instrucción y se ha resaltado en amarillo las señales con
las que se comprueba que la operación se ejecuta correctamente. Si se observa junto con el diagrama
de estados de la unidad de control, se aprecia la correcta transición de estos y como en el último
estado, el que ejecuta la operación, se aprecia los operandos en las señales de salida de los registros A
y B, A_OUT y B_OUT, el resultado en la señal de entrada del banco de registros REG_DI y la activación
de la escritura en este, ERWR. Puede apreciarse, además, el contador de programa, la instrucción en
lenguaje máquina en hexadecimal y como, una vez salen las direcciones del decodificador de
programa, actúan el resto de componentes para llevar a cabo la operación.
Las señales CLK_FPGA y RST_FPGA, son las entradas de la entidad que controlan todo el conjunto,
marcando el ritmo de funcionamiento y reiniciando todas las señales, respectivamente. ESTADO
muestra en qué estado se encuentra la unidad de control durante ese periodo de tiempo y se visualiza
para poder comprender como se está moviendo el diseño en el diagrama de transiciones de esta (véase
capítulo 3.3). EPC, PC, INSTR, FUNCT3, FUNCT7 y OPCODE se encuentran en todas las simulaciones (a
excepción de FUNCT7 ya que es exclusiva de este formato) ya que independientemente de que
formato sea la instrucción, debe hacerse el fetch de esta. De esta manera se muestra como se está
leyendo y decodificando cada una.
Memoria
56
Figura 4.2-3 Captura de los valores del banco de registro una vez completadas todas las instrucciones de formato R en la simulación.
Este tipo de captura se encuentra en prácticamente todos los resultados de la simulación y nos muestra
el contenido de los distintos registros del banco de registros una vez se ha ejecutado todo el programa.
La columna de la izquierda marca con base decimal el valor de la dirección del registro que es encuentra
a la izquierda de esa fila (por lo tanto, el registro con dirección 7 contiene un valor “7”, el registro con
dirección 15 “15, el 23 “-1” y el 31 “0”). La dirección del registro que contiene los valores que se
muestran se puede extender teniendo en cuenta que abajo a la derecha se encuentra el registro x0 y
se sigue de derecha a izquierda y de abajo a arriba.
Los registros resaltados en amarillo contienen el resultado de las operaciones, siendo el “5” el
contenido del registro perteneciente a la primera instrucción, el x22, y el “0” el de la última, el x31. El
resto de valores coincide con los de la Tabla 4-2, ya que en estos no se escribe.
Teniendo en cuenta que en el cronograma se observa la operación que debe ejecutar el diseño paso a
paso y que el contenido de los registros destino coincide con el resultado de la operación, se verifica
que las instrucciones de formato R funcionan correctamente.
Diseño, implementación y test de un core RISC-V
57
4.3 Instrucciones de formato I Computacionales
Como las instrucciones de formato I computacionales tan solo difieren con las de formato R por el uso
de un inmediato reemplazando el registro fuente 2, tanto el programa como los resultados son
significativamente similares.
4.3.1 Explicación del programa
El programa simulado es el siguiente:
Tabla 4-4 Programa usado para simular las instrucciones de formato I Computacionales.
Ensamblador Máquina (rs1) imm Operación Resultado
ADDI x23,x2,3 00310B93 210 310 2 + 3 510
SLTI x24,x4,3 00322C13 -410 310 -4 < 3 110
SLTIU x25,x4,3 00323C93 -410 310 4 < 3 010
XORI x26,x12,10 00A64D13 1210 1010 1100 xor 1010 01102 = 610
ORI x27,x12,10 00A66D93 1210 1010 1100 or 1010 11102 = 1410
ANDI x28,x12,10 00A67E13 1210 1010 1100 and 1010 10002 = 810
SLLI x29,x15,1 00179E93 1510 110 15 x 21 3010
SRLI x30,x12,2 006A5F13 1210 210 12 / 22 310
SRAI 31,x20,1030 406A5F93 -22510 103010 -225 / 26 -219
10 = -52428810
Teniendo en cuenta que tanto los registros fuente 1 y destino coinciden con la tabla 4.2, el resultado
debe ser el mismo, puesto que, además, el inmediato de tipo I coincide con el contenido de los registros
fuente 2 usado en la simulación de las instrucciones de formato R.
En la instrucción SRAI se observa que el inmediato tiene un valor de 1030 pero se opera con un valor
6. Si en la simulación la ALU procede de la misma manera significa que se ha cumplido lo especificado
en el capítulo 3.2.4 sobre las instrucciones de desplazamiento, que se extrae los 5 bits de menos peso
para el operando B para que el desplazamiento sea de 0 a 31 bits. Teniendo en cuenta el valor 1030 en
binario, se observa cómo se extrae el valor 6:
103010 = 0100000001102 => 0011002 = 610
Memoria
58
4.3.2 Resultados de la simulación
Figura 4.3-1 Primera fila de resultados de la simulación de las instrucciones de formato I computacionales.
Diseño, implementación y test de un core RISC-V
59
Figura 4.3-2 Segunda fila de resultados de la simulación de las instrucciones de formato I computacionales.
En el cronograma se visualiza como se decodifica el inmediato definido, se selecciona y se usa como
operando B en la operación en la unidad aritmético-lógica de la misma forma que se hacía con el
registro fuente 2.
Figura 4.3-3 Captura de los valores del banco de registro una vez completadas todas las instrucciones de formato I
computacionales en la simulación.
Memoria
60
4.4 Instrucciones de formato I
Las instrucciones de formato I restantes son de lectura de memoria y la instrucción JALR. Con la
finalidad de que se verifique todas las funciones del diseño, la instrucción LB se repite para acceder al
dispositivo de entrada. De esta forma comprobamos que se puede trabajar con un valor añadido desde
la placa donde se implementa el diseño. Como se muestra al inicio del capítulo, se ha simulado una
entrada fija con el valor “CC” en hexadecimal. Una vez este valor se guarde en un registro se podrá
trabajar con él.
4.4.1 Explicación del programa
El programa simulado es el siguiente:
Tabla 4-5 Programa usado para simular las instrucciones de formato I.
Ensamblador Máquina (rs1) imm Operación Resultado Contenido dir
memoria (rd)
LB x25,35(x8) 02340C83 9310 3510 93 + 35 128 DDCCBBAA FFFFFFAA
LH x26,35(x8) 02341D03 9310 3510 93 + 35 128 DDCCBBAA FFFFBBAA
LW x27, 35(x8) 02342D83 9310 3510 93 + 35 128 DDCCBBAA DDCCBBAA
LB x28,67(x13) 0436AE03 18910 6710 189 + 67 256 000000CC 000000CC
LBU x29,35(x8) 02344E83 9310 3510 93 + 35 128 DDCCBBAA 000000AA
LHU x30,35(x8) 02345F03 9310 3510 93 + 35 128 DDCCBBAA 0000BBAA
JALR x31,x3,1 00118FE7 310 110 3 + 1 4 - 0000001C
En el código ensamblador de las operaciones de lectura, se muestra primero el registro destino y
después el inmediato seguido del registro fuente 1 entre paréntesis. Estos dos últimos se suman para
obtener la dirección objetivo de la memoria de la cual se va a extraer su contenido para posteriormente
adaptar y escribir en el registro.
En todas las instrucciones se escoge la misma dirección para comprobar el funcionamiento de la
adaptación de la señal. La excepción, la instrucción con dirección objetivo 256, lee el registro del
dispositivo de entrada, al cual se le ha fijado un valor “CC”. El valor que sale de la memoria se extiende
automáticamente sin signo ya que tan solo se le permite hacer la lectura de un byte, por lo tanto, nunca
se usarán el resto de bits para este caso.
La instrucción JALR suma el contenido del registro x1 con el inmediato, que resulta en la dirección
objetivo, la cual es la segunda instrucción LH x26,35(x8). Este salto se puede apreciar en el contenido
del contador de programa en el siguiente cronograma. El resultado guardado en el registro destino es
la dirección de la siguiente instrucción a ejecutar tras JALR (24 + 4 = 2610 = 1C16).
Diseño, implementación y test de un core RISC-V
61
4.4.2 Resultados de la simulación
Figura 4.4-1 Primera página de la primera fila de resultados de la simulación de las instrucciones de formato I.
Memoria
62
Figura 4.4-2 Segunda página de la primera fila de resultados de la simulación de las instrucciones de formato I.
Diseño, implementación y test de un core RISC-V
63
Figura 4.4-3 Primera página de la segunda fila de resultados de la simulación de las instrucciones de formato I.
Memoria
64
Figura 4.4-4 Segunda página de la segunda fila de resultados de la simulación de las instrucciones de formato I.
Se observa que pasados 570 ns el contador de programa deja de sumar 4 a su valor para saltar a la
dirección objetivo obtenido de la suma de la instrucción JALR.
A diferencia de las instrucciones de formato I computacionales, en las instrucciones de lectura el
resultado de la operación se lleva a la entrada de dirección de la memoria RAM para poder leer el
contenido y adaptarlo antes de cargarlo en el registro destino. Las diferentes adaptaciones se pueden
observar en las señales DATA_HU, DATA_HS, DATA_BU y DATA_BS y su selección en D_SIGN y
D_LENGHT.
La dirección objetivo se obtiene sumando el contenido del registro fuente 1 y el inmediato de tipo I,
por lo que se aprecia su resultado en las mismas señales que en las anteriores simulaciones: A_OUT y
B_OUT para los operandos y ALU__OUT para el resultado. La señal DIR_MEM muestra el valor de
entrada de dirección de la memoria en todo momento, tanto para leer instrucciones como datos.
Debido a que cuando no se accede a la memoria RAM el bloque de memoria devuelve un valor de 0,
tan solo se muestra el valor leído de la memoria durante el estado correspondiente. Además de en el
cronograma, se puede verificar el resultado en la siguiente figura.
Diseño, implementación y test de un core RISC-V
65
Figura 4.4-5 Captura de los valores del banco de registro una vez completadas todas las instrucciones de formato I en la
simulación.
4.5 Instrucciones de formato S
Las instrucciones de carga de memoria conforman todas las de formato S. Como los datos se guardan
en la memoria RAM y no en el banco de registros, para verificar el resultado de esta simulación se
muestra los valores de las direcciones de la memoria RAM modificadas.
En este caso, se usa la instrucción SH para escribir un contenido definido en el dispositivo de salida
mapeado en la memoria y comprobar si se muestra en el monitor de la placa donde se implementa.
4.5.1 Explicación del programa
El programa simulado es el siguiente:
Tabla 4-6 Programa usado para simular las instrucciones de formato I.
Ensamblador Máquina (rs1) (rs2) imm Operación Resultado Contenido Mem
SB x9,39(x8) 029403A3 9310 E718FFAA16 39 93 + 39 132 000000AA
SH x9,43(x8) 029415A3 9310 E718FFAA16 43 93 + 43 136 0000FFAA
SW x9,47(x8) 029427A3 9310 E718FFAA16 47 93 + 47 140 E718FFAA
SH x14,69(x13) 04E692A3 18910 0000ABCD16 69 189 + 69 258 ABCD
Teniendo en cuenta que la memoria RAM comprende de la dirección 128 a 255, las direcciones objetivo
obtenidas de las instrucciones corresponden a sus direcciones 4, 8 y 12. Esto es debido al mapeado de
memoria creado en el capítulo 3.2.14). En la última instrucción se encuentra que la suma del offset y
el contenido del registro x13 resulta en la dirección objetivo de la memoria 25810, donde se encuentra
Memoria
66
el registro del dispositivo de salida. Teniendo en cuenta que inicialmente los contenidos de la memoria
RAM tienen un valor ‘0’, a excepción de las 4 primeras direcciones, se observa cómo tan solo se escribe
sobre la cantidad de bytes marcada por la instrucción.
4.5.2 Resultados de la simulación
Figura 4.5-1 Resultados de la simulación de las instrucciones de formato S.
Para cada instrucción se puede observar cómo se obtiene la dirección objetivo sumando el contenido
del registro fuente 1 al inmediato de tipo S y llevándolo a la entrada de la dirección de la memoria con
la señal I_D. Además de eso, se da el valor indicado a EMW y MWLE para que se guarde la cantidad de
bits necesarios de la señal R2O.
Se aprecia además como la señal SSEG pasa de mostrar un 0 a mostrar la letra ‘d’ en el monitor de la
placa. La señal AN cambia de dígito cada 1 ms, por lo que en este resultado no se puede mostrar el
funcionamiento completo de esta señal ni la de SSEG.
Diseño, implementación y test de un core RISC-V
67
Figura 4.5-2 Captura de los valores de la memoria RAM 0 una vez completadas las instrucciones de formato S..
Figura 4.5-3 Figura 4.10. Captura de los valores de la memoria RAM 1 una vez completadas las instrucciones de formato S.
Figura 4.5-4 Captura de los valores de la memoria RAM 2 una vez completadas las instrucciones de formato S.
Figura 4.5-5 Captura de los valores de la memoria RAM 3 una vez completadas las instrucciones de formato S.
Memoria
68
Cabe especificar que la memoria RAM se organiza inversamente al banco de registros, teniendo arriba
a la izquierda la dirección 0 y abajo a la derecha la dirección 31.
Como se especifica en el capítulo 3.1.3, la memoria RAM esta divida en 4 bloques de 1 byte con los que
se forman las palabras de 4 bytes. Como se explica en el capítulo nombrado, se separa el vector de la
dirección para referirse a direcciones de la RAM, ya que los 2 bits de más peso engloban todas sus
direcciones. Por lo tanto, la primera dirección de la memoria, 12810 o 0100000002, es la dirección 0 de
cada una de las memorias RAM.
Teniendo esto en cuenta, el resultado se encuentra separado entre las 4 RAM, albergando cada una
un byte de la palabra entera (RAM3 guarda el byte de más peso y RAM0 el de menos peso). Si se
observa, por ejemplo, la tercera instrucción, esta carga el contenido completo del registro fuente 2 en
la dirección 14010, que se traduce como 0100011002. Como los 2 bits de menos peso engloban un valor
de 0 a 3, solo se utilizan para un acceso desalineado a la memoria, pero no se cumple esta condición
en esta simulación. Por lo tanto, se está accediendo a la dirección 310 de todas las memorias RAM, (la
instrucción LW carga una palabra entera) ya que del vector de dirección se extrae los siguientes bits:
000112.
Si se extrae en orden el contenido de la dirección 3 de las memorias RAM, se obtiene E718FFAA16, valor
que coincide con el esperado en la Tabla 4-6.
4.6 Instrucciones de formato B
Las instrucciones de formato B corresponden a todas las de salto condicional, siendo el inmediato de
tipo B el offset que se suma a la dirección del contador de programa para obtener la dirección objetivo
de la instrucción a la que salta en caso de cumplirse la condición. La verificación de esta instrucción,
por lo tanto, se encuentra en la señal PC, la cual muestra la dirección de la instrucción que se ejecuta y
debe ser la correspondiente con el resultado de la operación anterior.
Diseño, implementación y test de un core RISC-V
69
4.6.1 Explicación del programa
El programa simulado es el siguiente:
Tabla 4-7 Programa usado para simular las instrucciones de formato B. (“u”: sin signo, “s”: con signo).
Ensamblador Máquina (rs1) (rs2) Condición Resultado imm Dirección
PC
Orden de ejecución esperado
BEQ x1,x10,14 00A08763 110 110 1 = 1 Se cumple 1410 0 1º
BEQ x1,x2,8 00208463 110 210 1 = 2 No se
cumple 810 4 7º
BNE x1,x2,12 00209663 110 210 1 ≠ 2 Se cumple 1210 8 8º
BNE x1,x10,8 00A09463 110 110 1 ≠ 1 No se
cumple 810 12 2º
BLT x4,x3,12 00324663 -410 310 -4 <S 3 Se cumple 1210 16 3º
BLT x3,x2,8 0021C463 310 210 3 <S 2 No se
cumple 810 20 9º
BGE x1,x10,12 00A0D663 110 110 1 ≥ S 1 Se cumple 1210 24 10º
BGE x4,x3,8 00325463 -410 310 -4 ≥ S 3 No se
cumple 810 28 4º
BLTU x2,x3,12 00316663 210 310 2 < U 3 Se cumple 1210 32 5º
BLTU x4,x3,8 00326463 -410 310 -4 < U 3 No se
cumple 810 36 11º
BGEU x2,x3,8 00317463 210 310 2 ≥ U 3 No se
cumple 810 40 12º
BGEU x4,x3,-40 FC327CE3 -410 310 -4 ≥ U 3 Se cumple -4010 44 6º
Extrayendo el contenido, se aprecia la condición que se debe cumplir. A partir de esto y el inmediato
que contiene cada instrucción, se puede prever el orden en el que se ejecutarán las instrucciones, pues
forman un bucle. Para relacionar el inmediato con el orden, debe considerarse que el inmediato se va
a sumar a la dirección actual de PC para obtener la nueva. Teniendo en cuenta que la distancia de
direcciones entre instrucciones es de 4, si se divide el inmediato entre este número se obtiene la
cantidad de instrucciones que se salta en caso de que se cumple la condición. En caso contrario se
ejecuta la siguiente.
En la siguiente figura se comprueba que la señal PC sigue el orden generado por las instrucciones de
salto condicional.
Memoria
70
4.6.2 Resultados de la simulación
Figura 4.6-1 Primera fila de resultados de la simulación de las instrucciones de formato B.
Diseño, implementación y test de un core RISC-V
71
Figura 4.6-2 Segunda fila de resultados de la simulación de las instrucciones de formato B.
Memoria
72
Figura 4.6-3 Tercera fila de resultados de la simulación de las instrucciones de formato B.
Además de la señal PC, se aprecia el procedimiento para completar cada instrucción. Se carga el
contenido de los registros fuente en los operandos de la ALU para obtener un valor en la señal de
estado FZ, que marca si el resultado es 0 o diferente de 0. Como el resultado no tiene ninguna función,
no es necesario mostrarlo. El valor de FZ, junto con FUNCT3 que indica que instrucción de salto
condicional se está ejecutando, marca si la condición se cumple o no. En caso positivo, el inmediato de
tipo B se selecciona como operando B, mientras que en caso negativo es la constante 4 la que se
escoge. Luego, en ambos casos, se suma a la dirección actual del contador de programa y se escribe
nuevamente en este.
Diseño, implementación y test de un core RISC-V
73
4.7 Instrucciones de formato U
Las instrucciones computacionales de carga contienen un inmediato de tipo U de 20 bits al que se le
añaden 12 ‘0’s a la derecha y se escriben en el registro destino directamente o sumándose a la dirección
actual del contador de programa. La simplicidad de este tipo de instrucción genera una simulación
sencilla, donde se debe comprobar la correcta decodificación y obtención del inmediato, su suma con
la dirección del contador de programa, en caso necesario, y su escritura en el registro destino.
4.7.1 Explicación del programa
El programa simulado es el siguiente:
Tabla 4-8 Programa usado para simular las instrucciones de formato U.
Ensamblador Máquina Imm PC Operación Resultado
LUI x30,1048575 FFFFFF37 FFFFF00016 016 - -
AUICP x31,5 00005F97 0000500016 416 00005000 + 4 0000500416
El inmediato de la instrucción LUI, mostrado en decimal, es FFFFF16. Se ha escogido este valor para
visualizar correctamente como se concatena con el vector de ceros y forma el inmediato final de 32
bits FFFFF00016. En el caso de la instrucción AUIPC, el inmediato final será 0000500016, el cual se suma
a la dirección actual del contador de programa antes de escribirse en el registro destino.
Memoria
74
4.7.2 Resultados de la simulación
Figura 4.7-1 Resultados de la simulación de las instrucciones de formato U.
Se aprecia claramente como la señal ERWR se activa en el instante en que el dato de entrada del banco
de registros, REG_DI, es el correspondiente. Además, para AUIPC, se guarda el inmediato U en el
operando B y la dirección del contador de programa en A, se suma y se guarda en el registro del banco
indicado por la dirección en RD.
Diseño, implementación y test de un core RISC-V
75
Figura 4.7-2 Captura de los valores del banco de registro una vez completadas todas las instrucciones de formato U en la
simulación.
Como se especifica en el programa, los registros x30 y x31 terminan con el valor del inmediato U, o la
suma del inmediato y la dirección del contador de programa, extraído de la instrucción.
4.8 Instrucciones de formato J
La única instrucción de formato J, JAL, es una de las instrucciones de salto incondicional,
concretamente la que hace un salto relativo a PC. Esto se entiende como que el inmediato equivale a
la diferencia entre la dirección actual y la dirección objetivo. Para poder comprobar su funcionamiento,
se debe añadir instrucciones extra y comprobar que el salto se efectúa correctamente a la dirección
especificada.
4.8.1 Explicación del programa
El programa simulado es el siguiente:
Tabla 4-9 Programa usado para simular las instrucciones de formato U. (solo se muestra la operación de las instrucciones importantes)
Ensamblador Máquina imm PC Operación Resultado
NOP 00000013 - - - -
XORI x0,x12,10 00A64013 - - - -
JAL,x31,-8 FF9FFFEF -8 8 -8 + 8 0
Memoria
76
La primera instrucción, NOP, realiza un ciclo de reloj sin que ocurra ningún cambio en cualquier
elemento del diseño (véase capítulo 3.1.1). Esta instrucción y XORI se han añadido para que el contador
de programa llegue al valor 8 desde el cual, la instrucción JAL debe devolverlo a la primera instrucción.
Para ello se ha asignado un inmediato de tipo J de valor -8. El registro destino indicado, x31, guarda el
valor de la dirección de la siguiente instrucción a JAL.
4.8.2 Resultados de la simulación
Figura 4.8-1 Resultados de la simulación de las instrucciones de formato J.
En la señal PC se aprecia como cuando su valor es 8, se extrae la tercera instrucción, JAL, y tras
completarla se actualiza el valor de la señal nuevamente a 0. De esta manera se ha creado un ciclo de
este programa. Para efectuarse, se decodifica y extrae el inmediato de tipo J y se añade al operando B,
mientras que en el operando A se guarda el valor del contador de programa. El resultado de la suma
se introduce al contador de programa, habiéndose adaptado previamente. Para ello se extrae los bits
8 a 2 del resultado y se agregan 2 ceros a la derecha. De esta manera la señal se adapta al tamaño del
registro “PC” y se asegura no guardar un valor no múltiplo de 4. En caso contrario se accedería a una
dirección desalineada de la memoria ROM, hecho que provocaría un fallo, pues la ROM se debe
acceder solo de manera alineada.
Diseño, implementación y test de un core RISC-V
77
Figura 4.8-2 Captura de los valores del banco de registro una vez completadas todas las instrucciones de formato J en la
simulación.
El registro x31 guarda, como se ha especificado, la dirección posterior a la que guarda la instrucción
JAL.
Diseño, implementación y test de un core RISC-V
79
5. Implementación del diseño
La fase final del proyecto, una vez se ha creado y verificado el diseño del core RISC-V, es implementarlo
en una FPGA para poder realizar pruebas sobre un modelo físico. De esta manera se comprueba si el
diseño es viable y apto para su utilización.
Para este proyecto se ha escogido la placa Nexys 2 de la empresa DIGILENT. Está basada en la familia
de FPGA Xilinx Spartan 3E y cuenta con un puerto USB2 desde donde se puede alimentar y conectar
con un ordenador, 16Mbytes de RAM y ROM, un reloj de 50MHz y varios puertos de entrada y salida.
De entre todos los puertos, se da uso al monitor de 4 dígitos de 7 segmentos y a los 8 interruptores
deslizantes.
Figura 5-1 FPGA Nexys 2 de la empresa DIGILENT. (14)
Para sintetizar el código VHDL y crear el archivo de programa con el que se podrá cargar el diseño a la
FPGA se ha utilizado el programa Xilinx ISE. Este software permite simular e implementar programas
de VHDL. Para este caso, tan solo se hace uso de la implementación, pues se ha simulado mediante
Modelsim.
Memoria
80
5.1 Síntesis
El primer paso ha sido sintetizar el código para verificar que es posible implementarlo en una placa y
obtener el informe de síntesis, desde donde comprobaremos si la FPGA soportará el diseño creado.
5.1.1 Memoria de bloque
Durante la etapa de simulación del diseño, una vez creado el código se compila para detectar errores
de sintaxis. En este caso, la comprobación se hace mediante síntesis, donde no solo se comprueba la
sintaxis, sino que se analiza el programa detectando su diseño y estructura. De esta manera se
encuentran avisos o errores que influyen en la implementación final.
La síntesis detecta el código de las memorias y en función de su diseño, las implementa como
memorias de bloque o memorias distribuidas.
Tabla 5-1 Esquema con las principales diferencias entre memoria distribuida o de bloque. (15)
Memoria distribuida Memoria de bloque
Escritura Síncrona Síncrona
Lectura Asíncrona Síncrona
La principal diferencia entre los dos tipos de memoria reside en la lectura de datos. Mientras la
memoria distribuida puede leer datos de manera asíncrona, la memoria de bloque lo hace
síncronamente.
La memoria distribuida supone una ventaja a la hora del diseño de la unidad de control, pues como se
especifica en el capítulo 3.3.1, permite que no sea necesario esperar un ciclo de reloj para poder extraer
la instrucción y decidir a qué estado pasar.
La otra diferencia, la cual se ha comprobado por el informe de síntesis, es que la memoria distribuida
hace uso de los dispositivos lógicos contenidos en la FPGA, mientras que la memoria de bloque está
insertada entre esta lógica. La ventaja por lo tanto es el menor uso de recursos, que se traduce como
una mejor implementación.
Diseño, implementación y test de un core RISC-V
81
5.1.2 Informe de síntesis
El informe de síntesis detalla sobre los resultados de una posible implementación del diseño en la placa
Nexys 2. Una vez verificado que el diseño es correcto y es posible, se estudia este informe para
comprobar que las especificaciones de esta placa pueden soportar las características del diseño creado.
Para ello se debe observar los informes de área y de temporización.
5.1.2.1 Informe de área
El informe de área resume el porcentaje de dispositivos que se requieren en el diseño sobre la cantidad
que contiene la placa. Un menor porcentaje se traduce como un diseño más favorable, pues una de las
metas del diseño lógico de un procesador es la simplicidad. En este caso, una vez sintetizado el diseño,
se obtiene la siguiente tabla:
Tabla 5-2 Tabla resumen del uso de dispositivos del programa Xilinx ISE.
En la tabla se aprecia como el uso de dispositivos se puede declarar reducido, haciendo que esta placa
sea capaz de ejecutar el procesador. Indica además la cantidad de dispositivos lógicos usados y la
cantidad que está destinada a cada uno de los posibles propósitos. Se observa además que las cinco
memorias RAM de bloque que se han asignado también son viables, pues la placa cuenta con un total
de 20.
Memoria
82
5.1.2.2 Informe de temporización
Con una estimación obtenida de la síntesis, el informe presenta un resumen de las temporizaciones
mínimas y máximas que es capaz de soportar el diseño. Este informe concluye si el reloj contenido en
la placa es compatible con la implementación de este procesador.
Esta compatibilidad radica en los períodos mínimos de un diseño. Teniendo en cuenta la tecnología de
los dispositivos lógicos de la placa y el diseño generado, se puede estimar los tiempos de retraso totales
presentes. Si el periodo de la señal de reloj, la cual temporiza el funcionamiento de todo el diseño, es
menor a estos tiempos de retraso se produciría una serie de errores que imposibilitaría la ejecución
del diseño.
Los resultados del informe de temporización son los siguientes:
Figura 5.1-1 Captura de los resultados del informe de temporización del programa Xilinx ISE.
Se observa que el diseño creado necesita un periodo mínimo de 17,319 ns para poder ejecutarse, lo
que a su vez significa que necesita una frecuencia máxima de 57,739 MHz. Si esta frecuencia fuese
superada, la placa no sería capaz de implementar el diseño. Teniendo en cuenta que la Nexys 2 cuenta
con un reloj de 50 MHz, podemos concluir que el diseño ha sido completamente verificado y está listo
para usar en esta placa (véase Anexo E. Fichero de restricciones ISE).
Diseño, implementación y test de un core RISC-V
83
6. Análisis del impacto ambiental
Al ser un proyecto de electrónica digital, el uso de energía y recursos es mínimo, pues se debe tener
en cuenta solo el gasto energético de los dispositivos usados, los cuales son el ordenador y la placa, la
cual se alimenta a partir del primero. Además, no se ha generado ningún tipo de residuo.
Se concluye, por lo tanto, que no es necesario ningún estudio sobre el impacto ambiental de este
proyecto, el cual es prácticamente nulo.
Diseño, implementación y test de un core RISC-V
85
7. Conclusiones
Los objetivos marcados al inicio del proyecto se han resuelto y documentado adecuadamente de
manera que quede comprensible todo el proceso llevado a cabo para resolver el alcance pretendido:
diseñar un micro procesador capacitado para ejecutar las 38 instrucciones que contiene el conjunto
base RV32I, el cual es parte del set de instrucciones RV32I, teniendo en cuenta además las pautas
otorgadas por el documento de especificaciones de esta.
También se ha podido verificar adecuadamente que su sintaxis es correcta y que es viable usar una
FPGA para ejecutarlo, puesto que los resultados de la síntesis han salido favorables respecto a las
características de la placa, las cuales son suficientes para poder trabajar este diseño tanto en
universidades como en trabajos futuros.
El hecho de usar una arquitectura abierta y que se pueda añadir más conjuntos de instrucciones a este
diseño da paso a posibles proyectos futuros con el objetivo de incrementar las posibilidades de este
microprocesador y sus características principales. Es por ello que se ha marcado paso a paso como se
ha generado el diseño, de manera que todo el proceso sea entendible.
Diseño, implementación y test de un core RISC-V
87
8. Análisis Económico
El análisis económico mostrado a continuación engloba los gastos generales del proyecto, formados
por el sueldo del ingeniero técnico industrial y el coste del material precisado. En cuanto a los costes
estructurales de la empresa a la que pertenece el propio ingeniero y el beneficio se desestiman para
este análisis.
Para calcular el sueldo del ingeniero se debe tener en cuenta el tiempo invertido en el proyecto.
Tabla 8-1. Desglose del tiempo invertido en el proyecto
Tareas a realizar Tiempo
invertido
Estudio y comprensión de la documentación necesaria para realizar el proyecto 60 h
Diseño del datapath 120 h
Diseño de la unidad de control 100 h
Programación en VHDL 160 h
Pruebas de simulación e implementación 80 h
Memoria 80 h
TOTAL 600 h
Estimando un sueldo de 22 €/h para un ingeniero técnico industrial, obtenemos el sueldo final:
𝑺𝒖𝒆𝒍𝒅𝒐 𝒅𝒆𝒍 𝒊𝒏𝒈𝒆𝒏𝒊𝒆𝒓𝒐 = 𝟔𝟎𝟎 𝒉 ∗ 𝟐𝟐€
𝒉= 𝟏𝟑. 𝟐𝟎𝟎 €
(Eq. 8.1)
A este sueldo se debe añadir el coste de los materiales y licencias necesarios para el desarrollo del
proyecto:
Memoria
88
Tabla 8-2 Desglose del material utilizado en el proyecto
Material Coste del
producto Amortización
Coste atribuido al
proyecto
Ordenador 1000 € 5 años 66,67 €
Periféricos 240 € 5 años 160 €
Digilent Nexys 2 192 € 5 años 12,8 €
Office 69 € 5 años 4,6 €
Windows 10 Home 145 € 5 años 9,6 €
ModelSim PE
Student Edition 0 0 € 5 años 0 €
Xilinx ISE WebPack 0 € 5 años 0 €
TOTAL 253,67 €
Para obtener el coste atribuido al proyecto se ha utilizado la siguiente ecuación:
𝑪𝒐𝒔𝒕𝒆 𝒂𝒕𝒓𝒊𝒃𝒖𝒊𝒅𝒐 𝒂𝒍 𝒑𝒓𝒐𝒚𝒆𝒄𝒕𝒐 = 𝑪𝒐𝒔𝒕𝒆 𝒅𝒆𝒍 𝒑𝒓𝒐𝒅𝒖𝒄𝒕𝒐 ∗ 𝑻𝒊𝒆𝒎𝒑𝒐 𝒅𝒆𝒍 𝒑𝒓𝒐𝒚𝒆𝒄𝒕𝒐
𝑻𝒊𝒆𝒎𝒑𝒐 𝒅𝒆 𝒂𝒎𝒐𝒓𝒕𝒊𝒛𝒂𝒄𝒊ó𝒏
(Eq. 8.2)
Para el cálculo se estima un tiempo de proyecto de 4 meses, lo que dura el cuatrimestre durante el que
se ha realizado dicho proyecto.
Con ambos costes obtenemos el gasto final del proyecto:
𝑮𝒂𝒔𝒕𝒐 𝒇𝒊𝒏𝒂𝒍 𝒅𝒆𝒍 𝒑𝒓𝒐𝒚𝒆𝒄𝒕𝒐 = 𝟏𝟑. 𝟐𝟎𝟎 € + 𝟐𝟓𝟑, 𝟔𝟕 € = 𝟏𝟑. 𝟒𝟓𝟑, 𝟔𝟕 € (Eq. 8.3)
Diseño, implementación y test de un core RISC-V
89
9. Bibliografía 1. Plaza, R.A., Cózar, J.R. y Carrasco, E.D.G. Fundamentos de los computadores. 2000. ISBN 8474968550. 2. File:Von Neumann Architecture.svg - Wikimedia Commons. En: [en línea]. [consulta: 20 septiembre 2020]. Disponible en: https://commons.wikimedia.org/wiki/File:Von_Neumann_Architecture.svg. 3. Arquitectura de Von Neumann - Wikipedia, la enciclopedia libre. En: [en línea]. [consulta: 20 septiembre 2020]. Disponible en: https://es.wikipedia.org/wiki/Arquitectura_de_Von_Neumann. 4. File:Harvard architecture-es.svg - Wikimedia Commons. En: [en línea]. [consulta: 20 septiembre 2020]. Disponible en: https://commons.wikimedia.org/wiki/File:Harvard_architecture-es.svg. 5. Arquitectura Harvard - Wikipedia, la enciclopedia libre. En: [en línea]. [consulta: 20 septiembre 2020]. Disponible en: https://es.wikipedia.org/wiki/Arquitectura_Harvard. 6. Aguayo, P. Introducción a los microcontroladores. En: FrisWolker [en línea]. 2004, p. 1-15. Disponible en: http://scholar.google.com/scholar?hl=en&btnG=Search&q=intitle:INTRODUCCIÓN+AL+MICROCONTROLADOR#0. 7. Conjunto de instrucciones - Wikipedia, la enciclopedia libre. En: [en línea]. [consulta: 20 septiembre 2020]. Disponible en: https://es.wikipedia.org/wiki/Conjunto_de_instrucciones. 8. Hatamian, M. et al. In Praise of Digital Design and Computer Architecture. 2016. ISBN 9780123944245. DOI 10.1016/b978-0-12-800056-4.00022-4. 9. Patterson, D. y Waterson, A. Guía Práctica de RISC-V. En: [en línea]. 2017, Disponible en: http://riscvbook.com/spanish/guia-practica-de-risc-v-1.0.5.pdf. 10. File:Apple A13 Bionic.jpg - Wikimedia Commons. En: [en línea]. [consulta: 21 septiembre 2020]. Disponible en: https://commons.wikimedia.org/wiki/File:Apple_A13_Bionic.jpg. 11. GAP8 - GreenWaves - WikiChip. En: [en línea]. [consulta: 8 octubre 2020]. Disponible en: https://en.wikichip.org/wiki/greenwaves/gap8. 12. PULP Project Information. En: [en línea]. [consulta: 9 octubre 2020]. Disponible en: https://pulp-platform.org/projectinfo.html. 13. GitHub - pulp-platform/pulpino: An open-source microcontroller system based on RISC-V. En: [en línea]. [consulta: 9 octubre 2020]. Disponible en: https://github.com/pulp-platform/pulpino. 14. Digilent, C. Digilent Nexys 2 Board Reference Manual. En: . 2012, p. 1-17. 15. Vivado Design Suite User Guide: Synthesis. En: [en línea]. Disponible en: https://www.xilinx.com/support/documentation/sw_manuals/xilinx2020_2/ug901-vivado-synthesis.pdf.
Diseño, implementación y test de un core RISC-V
93
Anexo B. Descripción VHDL
B.1 TOP
1 library IEEE;
2 use IEEE.std_logic_1164.all;
3
4 entity TOP is
5 port( CLK_FPGA, RST_FPGA : in std_logic;
6 SW : in std_logic_vector (7 downto 0);
7 AN : out std_logic_vector (3 downto 0);
8 SSEG : out std_logic_vector (7 downto 0));
9 end TOP;
10
11 architecture STRUCT of TOP is
12
13 component DATAPATH is
14 port(
15 -- E/S TOP
16 CLK,RST : in std_logic;
17 SW : in std_logic_vector (7 downto 0);
18 AN : out std_logic_vector (3 downto 0);
19 SSEG : out std_logic_vector (7 downto 0);
20
21 -- E/S Unidad de Control
22 EPC,I_D,EMW,D_SIGN,ERWR,A_SEL,EAR,EBR : in std_logic;
23 MWLE,ISBJ_SEL,D_LENGHT,DI_SEL,B_SEL : in
std_logic_vector (1 downto 0);
24 ALU_CONTROL : in std_logic_vector (3 downto 0);
25 FZ: out std_logic;
26 FUNCT3 : out std_logic_vector (2 downto 0);
27 FUNCT7,OPCODE : out std_logic_vector (6 downto 0));
28 end component;
29
30 component UC is
31 port( CLK,RST,FZ : in std_logic;
32 FUNCT3 : in std_logic_vector (2 downto 0);
33 FUNCT7,OPCODE : in std_logic_vector (6 downto 0);
34 EPC,I_D,EMW,D_SIGN,ERWR,A_SEL,EAR,EBR : out std_logic;
35 MWLE,ISBJ_SEL,D_LENGHT,DI_SEL,B_SEL : out
std_logic_vector (1 downto 0);
36 ALU_CONTROL : out std_logic_vector (3 downto 0));
37 end component;
38
39 signal EPC : std_logic;
40 signal I_D : std_logic;
41 signal EMW : std_logic;
42 signal D_SIGN : std_logic;
43 signal ERWR : std_logic;
Annexos
94
44 signal A_SEL : std_logic;
45 signal EAR : std_logic;
46 signal EBR : std_logic;
47 signal MWLE : std_logic_vector (1 downto 0);
48 signal ISBJ_SEL : std_logic_vector (1 downto 0);
49 signal D_LENGHT : std_logic_vector (1 downto 0);
50 signal DI_SEL : std_logic_vector (1 downto 0);
51 signal B_SEL : std_logic_vector (1 downto 0);
52 signal ALU_CONTROL : std_logic_vector (3 downto 0);
53 signal FZ : std_logic;
54 signal FUNCT3 : std_logic_vector (2 downto 0);
55 signal FUNCT7 : std_logic_vector (6 downto 0);
56 signal OPCODE : std_logic_vector (6 downto 0);
57
58 begin
59
60 DATAPATH_0 : DATAPATH port map(
61 CLK => CLK_FPGA, RST => RST_FPGA, EPC => EPC, I_D => I_D, EMW
=> EMW,
62 D_SIGN => D_SIGN, ERWR => ERWR, A_SEL => A_SEL, EAR => EAR, EBR
=> EBR,
FZ => FZ, MWLE => MWLE,
63 ISBJ_SEL => ISBJ_SEL, D_LENGHT => D_LENGHT, DI_SEL => DI_SEL,
B_SEL => B_SEL
, ALU_CONTROL => ALU_CONTROL,
64 FUNCT3 => FUNCT3, FUNCT7 => FUNCT7, OPCODE => OPCODE, SW => SW,
AN => AN
, SSEG => SSEG);
65
66 UC_0 : UC port map(
67 CLK => CLK_FPGA, RST => RST_FPGA, EPC => EPC, I_D => I_D, EMW
=> EMW,
68 D_SIGN => D_SIGN, ERWR => ERWR, A_SEL => A_SEL, EAR => EAR, EBR
=> EBR,
FZ => FZ, MWLE => MWLE,
69 ISBJ_SEL => ISBJ_SEL, D_LENGHT => D_LENGHT, DI_SEL => DI_SEL,
B_SEL => B_SEL
, ALU_CONTROL => ALU_CONTROL,
70 FUNCT3 => FUNCT3, FUNCT7 => FUNCT7, OPCODE => OPCODE);
71 end STRUCT;
Diseño, implementación y test de un core RISC-V
95
B.2 Datapath
1 library IEEE;
2 use IEEE.STD_LOGIC_1164.all;
3 use IEEE.NUMERIC_STD.all;
4
5 entity DATAPATH is
6 port(
7 -- E/S TOP
8 CLK,RST : in std_logic;
9 SW : in std_logic_vector (7 downto 0);
10 AN : out std_logic_vector (3 downto 0);
11 SSEG : out std_logic_vector (7 downto 0);
12
13 -- E/S Unidad de Control
14 EPC,I_D,EMW,D_SIGN,ERWR,A_SEL,EAR,EBR : in std_logic;
15 MWLE,ISBJ_SEL,D_LENGHT,DI_SEL,B_SEL : in
std_logic_vector (1 downto 0);
16 ALU_CONTROL : in std_logic_vector (3 downto 0);
17 FZ: out std_logic;
18 FUNCT3 : out std_logic_vector (2 downto 0);
19 FUNCT7,OPCODE : out std_logic_vector (6 downto 0));
20 end DATAPATH;
21
22 architecture STRUCT of DATAPATH is
23
24 -- COMPONENTES
25
26 component ALU is
27 port( A,B : in std_logic_vector (31 downto 0);
28 O : in std_logic_vector (3 downto 0);
29 R : out std_logic_vector (31 downto 0);
30 Z : out std_logic);
31 end component;
32
33 component MEM_DATASIGN is
34 port( HALF_DATA : in std_logic_vector (15 downto 0);
35 DATA_HU,DATA_HS : out std_logic_vector (31 downto 0);
36 DATA_BU,DATA_BS : out std_logic_vector (31 downto 0));
37 end component;
38
39 component INST_DECODER is
40 port( INSTR : in std_logic_vector (31 downto 0);
41 RS1,RS2,RD : out std_logic_vector (4 downto 0);
42 FUNCT3 : out std_logic_vector (2 downto 0);
43 FUNCT7,OPCODE : out std_logic_vector (6 downto 0);
44 I_IMM,S_IMM,B_IMM,J_IMM,U_IMM : out std_logic_vector
(31 downto 0));
45 end component;
46
47 component MEM_DATAINSTR is
48 port( CLK, CLR, WE : in std_logic;
Annexos
96
49 WL : in std_logic_vector (1 downto 0);
50 DIR : in std_logic_vector (8 downto 0);
51 DI : in std_logic_vector (31 downto 0);
52 DIP : in std_logic_vector (7 downto 0);
53 INSTR, DATA : out std_logic_vector (31 downto 0);
54 DOP : out std_logic_vector (15 downto 0));
55 end component;
56
57 component MUX2X1_32b is
58 port( I0,I1 : in std_logic_vector (31 downto 0);
59 S : in std_logic;
60 O : out std_logic_vector (31 downto 0));
61 end component;
62
63 component MUX2X1_9b is
64 port( I0,I1 : in std_logic_vector (8 downto 0);
65 S : in std_logic;
66 O : out std_logic_vector (8 downto 0));
67 end component;
68
69 component MUX_4X1 is
70 port( I0,I1,I2,I3 : in std_logic_vector (31 downto 0);
71 S : in std_logic_vector (1 downto 0);
72 O : out std_logic_vector (31 downto 0));
73 end component;
74
75 component REG_BANK is
76 port( CLK,WE : in std_logic;
77 A1,A2,AD : in std_logic_vector (4 downto 0);
78 DI : in std_logic_vector (31 downto 0);
79 DO1,DO2 : out std_logic_vector (31 downto 0));
80 end component;
81
82 component REGISTRO32 is
83 port( CLK, CLR, EN : in std_logic;
84 R32_IN : in std_logic_vector (31 downto 0);
85 R32_OUT : out std_logic_vector (31 downto 0));
86 end component;
87
88 component REGISTRO9 is
89 port( CLK, CLR, EN : in std_logic;
90 R9_IN : in std_logic_vector (8 downto 0);
91 R9_OUT : out std_logic_vector (8 downto 0));
92 end component;
93
94 component OUT_NEXYS2 is
95 port( CLK,RST : in std_logic;
96 P_OUT : in std_logic_vector (15 downto 0);
97 AN : out std_logic_vector (3 downto 0);
98 SSEG : out std_logic_vector (7 downto 0));
99 end component;
100
Diseño, implementación y test de un core RISC-V
97
101 -- SEÑALES
102
103 signal PC : std_logic_vector (8 downto 0);
104 signal PC_IN : std_logic_vector (8 downto 0);
105 signal APC : std_logic_vector (31 downto 0);
106 signal DIR_MEM : std_logic_vector (8 downto 0);
107 signal INSTR : std_logic_vector (31 downto 0);
108 signal DATA : std_logic_vector (31 downto 0);
109 signal HALF_DATA : std_logic_vector (15 downto 0);
110 signal DATA_HU : std_logic_vector (31 downto 0);
111 signal DATA_HS : std_logic_vector (31 downto 0);
112 signal DATA_BU : std_logic_vector (31 downto 0);
113 signal DATA_BS : std_logic_vector (31 downto 0);
114 signal DATA_H : std_logic_vector (31 downto 0);
115 signal DATA_B : std_logic_vector (31 downto 0);
116 signal MEMDATA_R : std_logic_vector (31 downto 0);
117 signal RS1 : std_logic_vector (4 downto 0);
118 signal RS2 : std_logic_vector (4 downto 0);
119 signal RD : std_logic_vector (4 downto 0);
120 signal I_IMM : std_logic_vector (31 downto 0);
121 signal S_IMM : std_logic_vector (31 downto 0);
122 signal B_IMM : std_logic_vector (31 downto 0);
123 signal J_IMM : std_logic_vector (31 downto 0);
124 signal U_IMM : std_logic_vector (31 downto 0);
125 signal ISBJ_IMM : std_logic_vector (31 downto 0);
126 signal REG_DI : std_logic_vector (31 downto 0);
127 signal R1O : std_logic_vector (31 downto 0);
128 signal R2O : std_logic_vector (31 downto 0);
129 signal A_IN : std_logic_vector (31 downto 0);
130 signal B_IN : std_logic_vector (31 downto 0);
131 signal A_OUT : std_logic_vector (31 downto 0);
132 signal B_OUT : std_logic_vector (31 downto 0);
133 signal ALU_OUT : std_logic_vector (31 downto 0);
134 signal P_OUT : std_logic_vector (15 downto 0);
135
136 constant CONST0 : std_logic_vector :=
std_logic_vector(to_unsigned(0, 32));
137 constant CONST4 : std_logic_vector :=
std_logic_vector(to_unsigned(4, 32));
138
139 begin
140
141 -- Adaptación de señales
142
143 PC_IN <= ALU_OUT (8 downto 2)&"00";
144 APC <= std_logic_vector(to_unsigned(0, 23))&PC;
145 HALF_DATA <= DATA (15 downto 0);
146
147 -- MAPEADO DE COMPONENTES
148
149 ALU_0 : ALU port map(
150 A => A_OUT,
Annexos
98
151 B => B_OUT,
152 O => ALU_CONTROL,
153 Z => FZ,
154 R => ALU_OUT);
155
156 MEM_DATASIGN_0 : MEM_DATASIGN port map(
157 HALF_DATA => HALF_DATA,
158 DATA_HU => DATA_HU,
159 DATA_HS => DATA_HS,
160 DATA_BU => DATA_BU,
161 DATA_BS => DATA_BS);
162
163 INST_DECODER_0 : INST_DECODER port map(
164 INSTR => INSTR,
165 RS1 => RS1,
166 RS2 => RS2,
167 RD => RD,
168 FUNCT3 => FUNCT3,
169 FUNCT7 => FUNCT7,
170 OPCODE => OPCODE,
171 I_IMM => I_IMM,
172 S_IMM => S_IMM,
173 B_IMM => B_IMM,
174 J_IMM => J_IMM,
175 U_IMM => U_IMM);
176
177 MEM_DATAINSTR_0 : MEM_DATAINSTR port map(
178 CLK => CLK,
179 CLR => RST,
180 WE => EMW,
181 WL => MWLE,
182 DIR => DIR_MEM,
183 DI => R2O,
184 DIP => SW,
185 INSTR => INSTR,
186 DATA => DATA,
187 DOP => P_OUT);
188
189 ID_MUX : MUX2X1_9b port map(
190 I0 => PC,
191 I1 => ALU_OUT (8 downto 0),
192 S => I_D,
193 O => DIR_MEM);
194
195 SIGN_H : MUX2X1_32b port map(
196 I0 => DATA_HS,
197 I1 => DATA_HU,
198 S => D_SIGN,
199 O => DATA_H);
200
201 SIGN_B : MUX2X1_32b port map(
202 I0 => DATA_BS,
Diseño, implementación y test de un core RISC-V
99
203 I1 => DATA_BU,
204 S => D_SIGN,
205 O => DATA_B);
206
207 A_MUX : MUX2X1_32b port map(
208 I0 => APC,
209 I1 => R1O,
210 S => A_SEL,
211 O => A_IN);
212
213 ISBJ_MUX : MUX_4X1 port map(
214 I0 => I_IMM,
215 I1 => S_IMM,
216 I2 => B_IMM,
217 I3 => J_IMM,
218 S => ISBJ_SEL,
219 O => ISBJ_IMM);
220
221 DLENGHT_MUX : MUX_4X1 port map(
222 I0 => DATA,
223 I1 => DATA_H,
224 I2 => DATA_B,
225 I3 => CONST0,
226 S => D_LENGHT,
227 O => MEMDATA_R);
228
229 DI_MUX : MUX_4X1 port map(
230 I0 => U_IMM,
231 I1 => MEMDATA_R,
232 I2 => ALU_OUT,
233 I3 => CONST0,
234 S => DI_SEL,
235 O => REG_DI);
236
237 B_MUX : MUX_4X1 port map(
238 I0 => CONST4,
239 I1 => U_IMM,
240 I2 => ISBJ_IMM,
241 I3 => R2O,
242 S => B_SEL,
243 O => B_IN);
244
245 REG_BANK_0 : REG_BANK port map(
246 CLK => CLK,
247 WE => ERWR,
248 DI => REG_DI,
249 A1 => RS1,
250 A2 => RS2,
251 AD => RD,
252 DO1 => R1O,
253 DO2 => R2O);
254
Annexos
100
255 PCR : REGISTRO9 port map(
256 CLK => CLK,
257 CLR => RST,
258 EN => EPC,
259 R9_IN => PC_IN,
260 R9_OUT => PC);
261
262 A_REG : REGISTRO32 port map(
263 CLK => CLK,
264 CLR => RST,
265 EN => EAR,
266 R32_IN => A_IN,
267 R32_OUT => A_OUT);
268
269 B_REG : REGISTRO32 port map(
270 CLK => CLK,
271 CLR => RST,
272 EN => EBR,
273 R32_IN => B_IN,
274 R32_OUT => B_OUT);
275
276 OUT_NEXYS2_0 : OUT_NEXYS2 port map(
277 CLK => CLK,
278 RST => RST,
279 P_OUT => P_OUT,
280 SSEG => SSEG,
281 AN => AN);
282
283 end STRUCT;
Diseño, implementación y test de un core RISC-V
101
B.3 ALU
1 library IEEE;
2 use IEEE.STD_LOGIC_1164.all;
3 use IEEE.NUMERIC_STD.all;
4
5 entity ALU is
6 port( A,B : in std_logic_vector (31 downto 0);
7 O : in std_logic_vector (3 downto 0);
8 R : out std_logic_vector (31 downto 0);
9 Z : out std_logic);
10 end ALU;
11
12 architecture LOGIC of ALU is
13
14 signal O1,O2 : std_logic_vector (31 downto 0);
15 signal SHAMT : integer;
16 signal ALU_OUT : std_logic_vector (31 downto 0);
17
18 begin
19
20 SHAMT <= to_integer(unsigned(B(4 downto 0)));
21
22 with O select
23 ALU_OUT <= std_logic_vector(signed(A) + signed(B)) when
"0000", -- ADD
24 std_logic_vector(signed(A) - signed(B)) when
"0001", -- SUB
25 A xor B when "1000", -- XOR
26 A or B when "1100", -- OR
27 A and B when "1110", -- AND
28 std_logic_vector(shift_left(unsigned(A),SHAMT))
when "0010", -- SLL
29 std_logic_vector(shift_right(unsigned(A),SHAMT))
when "1010", -- SRL
30 std_logic_vector(shift_right(signed(A),SHAMT))
when "1011", -- SRA
31 O1 when others;
32
33 O1 <= std_logic_vector(to_unsigned(1, 32)) when ((signed(A) <
signed(B)) and (O =
"0100")) else O2; -- SLT
34 O2 <= std_logic_vector(to_unsigned(1, 32)) when ((unsigned(A) <
unsigned(B)) and (O =
"0110")) else (others => '0'); -- SLTU
35
36 -- FLAG ZERO
37 Z <= '1' when (ALU_OUT = std_logic_vector(to_unsigned(0, 32)))
else '0';
38
39 -- SALIDA
40 R <= ALU_OUT;
Annexos
102
41
42 end LOGIC;
B.4 Data_SignExtend
1 library IEEE;
2 use IEEE.STD_LOGIC_1164.all;
3 use IEEE.NUMERIC_STD.all;
4
5 entity MEM_DATASIGN is
6 port( HALF_DATA : in std_logic_vector (15 downto 0);
7 DATA_HU,DATA_HS : out std_logic_vector (31 downto 0);
8 DATA_BU,DATA_BS : out std_logic_vector (31 downto 0));
9 end MEM_DATASIGN;
10
11 architecture DATAFLOW of MEM_DATASIGN is
12 begin
13
14 DATA_HU <= std_logic_vector(resize(unsigned(HALF_DATA), 32));
15 DATA_HS <= std_logic_vector(resize(signed(HALF_DATA), 32));
16
17 DATA_BU <= std_logic_vector(resize(unsigned(HALF_DATA), 32));
18 DATA_BS <= std_logic_vector(resize(signed(HALF_DATA), 32));
19
20 end DATAFLOW;
B.5 Inst_Decoder
1 library IEEE;
2 use IEEE.STD_LOGIC_1164.all;
3 use IEEE.NUMERIC_STD.all;
4
5 entity INST_DECODER is
6 port( INSTR : in std_logic_vector (31 downto 0);
7 RS1,RS2,RD : out std_logic_vector (4 downto 0);
8 FUNCT3 : out std_logic_vector (2 downto 0);
9 FUNCT7,OPCODE : out std_logic_vector (6 downto 0);
10 I_IMM,S_IMM,B_IMM,J_IMM,U_IMM : out std_logic_vector
(31 downto 0));
11 end INST_DECODER;
12
13 architecture DATAFLOW of INST_DECODER is
14 begin
15
16 RS1 <= INSTR (19 downto 15);
17 RS2 <= INSTR (24 downto 20);
18 RD <= INSTR (11 downto 7);
19
20 FUNCT3 <= INSTR (14 downto 12);
Diseño, implementación y test de un core RISC-V
103
21 FUNCT7 <= INSTR (31 downto 25);
22 OPCODE <= INSTR (6 downto 0);
23
24 I_IMM <= std_logic_vector(resize(signed(INSTR(31) & INSTR(30
downto 25) & INSTR(24
downto 21) & INSTR(20)), 32));
25 S_IMM <= std_logic_vector(resize(signed(INSTR(31) & INSTR(30
downto 25) & INSTR(11
downto 8) & INSTR(7)), 32));
26 B_IMM <= std_logic_vector(resize(signed(INSTR(31) & INSTR(7) &
INSTR(30 downto 25) &
INSTR(11 downto 9) & "00"), 32)); --Se ha cambiado el ultimo bit
por un 0 ya que no añadimos la extension de 16 bits
27 J_IMM <= std_logic_vector(resize(signed(INSTR(31) & INSTR(19
downto 12) & INSTR(20) &
INSTR(30 downto 25) & INSTR(24 downto 22) & "00"), 32));
28 U_IMM <= INSTR(31)&INSTR(30 downto 20)&INSTR(19 downto
12)&std_logic_vector(
to_unsigned(0, 12));
29
30 end DATAFLOW;
B.6 Mem_DataInstr
1 library IEEE;
2 use IEEE.STD_LOGIC_1164.all;
3 use IEEE.STD_LOGIC_UNSIGNED.all;
4 use IEEE.NUMERIC_STD.all;
5
6 entity MEM_DATAINSTR is
7 port( CLK, CLR, WE : in std_logic;
8 WL : in std_logic_vector (1 downto 0);
9 DIR : in std_logic_vector (8 downto 0);
10 DI : in std_logic_vector (31 downto 0);
11 DIP : in std_logic_vector (7 downto 0);
12 INSTR,DATA : out std_logic_vector (31 downto 0);
13 DOP : out std_logic_vector (15 downto 0));
14 end MEM_DATAINSTR;
15
16 architecture STRUCT of MEM_DATAINSTR is
17
18 -- Componentes
19
20 component COD_MEM is
21 port( CLK : in std_logic;
22 DIR : in std_logic_vector (8 downto 0);
23 WL : in std_logic_vector (1 downto 0);
24 CS_IN : out std_logic;
25 CS_ROM : out std_logic;
26 CS_OUT0,CS_OUT1 : out std_logic;
Annexos
104
27 CS_RAM0,CS_RAM1,CS_RAM2,CS_RAM3 : out std_logic;
28 RAM0_1,RAM1_1,RAM2_1 : out std_logic;
29 CS : out std_logic_vector (1 downto 0));
30 end component;
31
32 component MUX2X1_5B is
33 port( I0,I1 : in std_logic_vector (4 downto 0);
34 S : in std_logic;
35 O : out std_logic_vector (4 downto 0));
36 end component;
37
38 component REGISTRO_IN is
39 port( CLK, EN : in std_logic;
40 RI_IN : in std_logic_vector (7 downto 0);
41 RI_OUT : out std_logic_vector (7 downto 0));
42 end component;
43
44 component REGISTRO_OUT is
45 port( CLK, CLR, EN, WE : in std_logic;
46 RO_IN : in std_logic_vector (7 downto 0);
47 RO_OUT : out std_logic_vector (7 downto 0));
48 end component;
49
50 component MEM_RAM is
51 port( CLK, CLR, WE, EN0, EN1, EN2, EN3 : in std_logic;
52 AD0, AD1, AD2, AD3 : in std_logic_vector (4 downto 0);
53 DI0, DI1, DI2, DI3 : in std_logic_vector (7 downto 0);
54 DIR_RAM : IN std_logic_vector (1 downto 0);
55 DO : out std_logic_vector (31 downto 0));
56 end component;
57
58 component MEM_ROM is
59 port( CLK, EN : in std_logic;
60 AD : in std_logic_vector(4 downto 0);
61 DO : out std_logic_vector(31 downto 0));
62 end component;
63
64 component MUX_4X1 is
65 port( I0,I1,I2,I3 : in std_logic_vector (31 downto 0);
66 S : in std_logic_vector (1 downto 0);
67 O : out std_logic_vector (31 downto 0));
68 end component;
69
70 -- Señales
71
72 signal DISP_IN_D : std_logic_vector (7 downto 0);
73 signal DISP_IN_D32 : std_logic_vector (31 downto 0);
74 signal DISP_OUT0_D : std_logic_vector (7 downto 0);
75 signal DISP_OUT1_D : std_logic_vector (7 downto 0);
76 signal DISP_OUT_D32 : std_logic_vector (31 downto 0);
77 signal DI0 : std_logic_vector (7 downto 0);
78 signal DI1 : std_logic_vector (7 downto 0);
Diseño, implementación y test de un core RISC-V
105
79 signal DI2 : std_logic_vector (7 downto 0);
80 signal DI3 : std_logic_vector (7 downto 0);
81 signal DIR62 : std_logic_vector (4 downto 0);
82 signal DIR10 : std_logic_vector (1 downto 0);
83 signal DIR62_1 : std_logic_vector (4 downto 0);
84 signal RAM_D : std_logic_vector (31 downto 0);
85 signal RAM0_AD :std_logic_vector (4 downto 0);
86 signal RAM1_AD :std_logic_vector (4 downto 0);
87 signal RAM2_AD :std_logic_vector (4 downto 0);
88 signal CS_IN : std_logic;
89 signal CS_ROM : std_logic;
90 signal CS_OUT0,CS_OUT1 : std_logic;
91 signal CS_RAM0,CS_RAM1,CS_RAM2,CS_RAM3 : std_logic;
92 signal RAM0_1,RAM1_1,RAM2_1 : std_logic;
93 signal CS : std_logic_vector (1 downto 0);
94
95 constant CONST0_16 : std_logic_vector :=
std_logic_vector(to_unsigned(0, 16));
96 constant CONST0_24 : std_logic_vector :=
std_logic_vector(to_unsigned(0, 24));
97 constant CONST0_32 : std_logic_vector :=
std_logic_vector(to_unsigned(0, 32));
98 constant CONST1 : std_logic_vector :=
std_logic_vector(to_unsigned(1, 32));
99
100 begin
101
102 -- Adaptación de señales
103
104 DISP_IN_D32 <= CONST0_24&DISP_IN_D;
105 DISP_OUT_D32 <= CONST0_16&DISP_OUT1_D&DISP_OUT0_D;
106
107 -- Separación dirección y datos de entrada
108
109 DI0 <= DI (7 downto 0);
110 DI1 <= DI (15 downto 8);
111 DI2 <= DI (23 downto 16);
112 DI3 <= DI (31 downto 24);
113
114 DIR62 <= DIR (6 downto 2);
115 DIR10 <= DIR (1 downto 0);
116
117 -- Suma de dirección
118
119 DIR62_1 <= DIR62 + 1;
120
121 -- Salida Puerto
122
123 DOP <= DISP_OUT1_D&DISP_OUT0_D;
124
125 -- Mapeado de componentes
126
Annexos
106
127 COD_MEM_0 : COD_MEM port map(
128 CLK => CLK,
129 DIR => DIR,
130 WL => WL,
131 CS_IN => CS_IN,
132 CS_ROM => CS_ROM,
133 CS_OUT0 => CS_OUT0,
134 CS_OUT1 => CS_OUT1,
135 CS_RAM0 => CS_RAM0,
136 CS_RAM1 => CS_RAM1,
137 CS_RAM2 => CS_RAM2,
138 CS_RAM3 => CS_RAM3,
139 RAM0_1 => RAM0_1,
140 RAM1_1 => RAM1_1,
141 RAM2_1 => RAM2_1,
142 CS => CS);
143
144 DISP_IN : REGISTRO_IN port map(
145 CLK => CLK,
146 EN => CS_IN,
147 RI_IN => DIP,
148 RI_OUT => DISP_IN_D);
149
150 DISP_OUT0 : REGISTRO_OUT port map(
151 CLK => CLK,
152 CLR => CLR,
153 WE => WE,
154 EN => CS_OUT0,
155 RO_IN => DI0,
156 RO_OUT => DISP_OUT0_D);
157
158 DISP_OUT1 : REGISTRO_OUT port map(
159 CLK => CLK,
160 CLR => CLR,
161 WE => WE,
162 EN => CS_OUT1,
163 RO_IN => DI1,
164 RO_OUT => DISP_OUT1_D);
165
166 MUX_RAM0 : MUX2X1_5B port map(
167 I0 => DIR62,
168 I1 => DIR62_1,
169 S => RAM0_1,
170 O => RAM0_AD);
171 172 MUX_RAM1 : MUX2X1_5B port map(
173 I0 => DIR62,
174 I1 => DIR62_1,
175 S => RAM1_1,
176 O => RAM1_AD);
177
178 MUX_RAM2 : MUX2X1_5B port map(
Diseño, implementación y test de un core RISC-V
107
179 I0 => DIR62,
180 I1 => DIR62_1,
181 S => RAM2_1,
182 O => RAM2_AD);
183
184 RAM : MEM_RAM port map(
185 CLK => CLK,
186 CLR => CLR,
187 WE => WE,
188 EN0 => CS_RAM0,
189 EN1 => CS_RAM1,
190 EN2 => CS_RAM2,
191 EN3 => CS_RAM3,
192 AD0 => RAM0_AD,
193 AD1 => RAM1_AD,
194 AD2 => RAM2_AD,
195 AD3 => DIR62,
196 DI0 => DI0,
197 DI1 => DI1,
198 DI2 => DI2,
199 DI3 => DI3,
200 DIR_RAM => DIR10,
201 DO => RAM_D);
202
203 ROM : MEM_ROM port map(
204 CLK => CLK,
205 EN => CS_ROM,
206 AD => DIR62,
207 DO => INSTR);
208
209 DATA_SEL : MUX_4X1 port map(
210 I0 => RAM_D,
211 I1 => DISP_OUT_D32,
212 I2 => DISP_IN_D32,
213 I3 => CONST0_32,
214 S => CS,
215 O => DATA);
216
217 end STRUCT;
Annexos
108
B.7 Codificacion_Mem
1 library IEEE;
2 use IEEE.STD_LOGIC_1164.all;
3
4 entity COD_MEM is
5 port( CLK : in std_logic;
6 DIR : in std_logic_vector (8 downto 0);
7 WL : in std_logic_vector (1 downto 0);
8 CS_IN : out std_logic;
9 CS_ROM : out std_logic;
10 CS_OUT0,CS_OUT1 : out std_logic;
11 CS_RAM0,CS_RAM1,CS_RAM2,CS_RAM3 : out std_logic;
12 RAM0_1,RAM1_1,RAM2_1 : out std_logic;
13 CS : out std_logic_vector (1 downto 0));
14 end COD_MEM;
15
16 architecture DATAFLOW of COD_MEM is
17
18 signal CS_OUT,CS_RAM : std_logic;
19 signal SEL_OUT,SEL_RAM : std_logic_vector (3 downto 0);
20
21 signal CS_IN_AUX : std_logic;
22
23 begin
24
25 -- Chip select
26
27 CS_IN_AUX <= '1' when (DIR = "100000000") else '0';
28 CS_ROM <= '1' when (DIR (8 downto 7) = "00") else '0';
29 CS_OUT <= '1' when (DIR (8 downto 1) = "10000001") else '0';
30 CS_RAM <= '1' when (DIR (8 downto 7) = "01") else '0';
31
32 -- Disp_Out select
33
34 SEL_OUT <= CS_OUT&WL&DIR(0);
35
36 CS_OUT0 <= '1' when (SEL_OUT = "1000" or SEL_OUT = "1010") else
'0';
37 CS_OUT1 <= '1' when (SEL_OUT = "1001" or SEL_OUT = "1010") else
'0';
38
39 -- RAM select
40
41 SEL_RAM <= WL&DIR (1 downto 0);
42
43 CS_RAM0 <= '1' when (CS_RAM = '1' and (DIR (1 downto 0) = "00"
or WL = "10" or SEL_RAM
= "0111")) else '0';
44 RAM0_1 <= '1' when (CS_RAM = '1' and (SEL_RAM = "0111" or
SEL_RAM = "1001" or SEL_RAM
= "1010" or SEL_RAM = "1011")) else '0';
Diseño, implementación y test de un core RISC-V
109
45
46 CS_RAM1 <= '1' when (CS_RAM = '1' and (DIR (1 downto 0) = "01"
or WL = "10" or SEL_RAM
= "0100")) else '0';
47 RAM1_1 <= '1' when (CS_RAM = '1' and (SEL_RAM = "1010" or
SEL_RAM = "1011" )) else '0';
48
49 CS_RAM2 <= '1' when (CS_RAM = '1' and (DIR (1 downto 0) = "10"
or WL = "10" or SEL_RAM
= "0101")) else '0';
50 RAM2_1 <= '1' when (CS_RAM = '1' and (SEL_RAM = "1011")) else
'0';
51
52 CS_RAM3 <= '1' when (CS_RAM = '1' and (DIR (1 downto 0) = "11"
or WL = "10" or SEL_RAM = "0110")) else '0';
53
54 -- Load Select (sincrono)
55
56 process (CLK)
57 begin
58 if rising_edge(CLK) then
59 if (CS_RAM = '1') then
60 CS <= "00";
61 elsif (CS_OUT = '1') then
62 CS <= "01";
63 elsif (CS_IN_AUX = '1') then
64 CS <= "10";
65 else
66 CS <= "11";
67 end if;
68 end if;
69 end process;
70
71 CS_IN <= CS_IN_AUX;
72
73 end DATAFLOW;
Annexos
110
B.8 Registro_IN
1 library IEEE;
2 use IEEE.STD_LOGIC_1164.all;
3
4 entity REGISTRO_IN is
5 port( CLK, EN : in std_logic;
6 RI_IN : in std_logic_vector (7 downto 0);
7 RI_OUT : out std_logic_vector (7 downto 0));
8 end REGISTRO_IN;
9
10 architecture BEH of REGISTRO_IN is
11 begin
12
13 process (CLK)
14 begin
15 if rising_edge(CLK) then
16 if (EN = '1') then
17 RI_OUT <= RI_IN;
18 end if;
19 end if;
20 end process;
21
22 end BEH;
B.9 Registro_OUT
1 library IEEE;
2 use IEEE.STD_LOGIC_1164.all;
3
4 entity REGISTRO_OUT is
5 port( CLK, CLR, EN, WE : in std_logic;
6 RO_IN : in std_logic_vector (7 downto 0);
7 RO_OUT : out std_logic_vector (7 downto 0));
8 end REGISTRO_OUT;
9
10 architecture BEH of REGISTRO_OUT is
11 begin
12
13 process (CLK)
14 begin
15 if rising_edge(CLK) then
16 if (CLR = '1') then
17 RO_OUT <= (others => '0');
18 elsif (EN = '1' and WE = '1') then
19 RO_OUT <= RO_IN;
20 end if;
21 end if;
22 end process;
23
Diseño, implementación y test de un core RISC-V
111
24 end BEH;
B.10 MUX2x1_5b
1 library IEEE;
2 use IEEE.STD_LOGIC_1164.all;
3
4 entity MUX2X1_5B is
5 port( I0,I1 : in std_logic_vector (4 downto 0);
6 S : in std_logic;
7 O : out std_logic_vector (4 downto 0));
8 end MUX2X1_5B;
9
10 architecture DATAFLOW of MUX2X1_5B is
11 begin
12
13 with S select
14 O <= I0 when '0',
15 I1 when others;
16
17 end DATAFLOW;
B.11 RAM
1 library IEEE;
2 use IEEE.STD_LOGIC_1164.all;
3 use IEEE.NUMERIC_STD.all;
4
5 entity MEM_RAM is
6 port( CLK, CLR, WE, EN0, EN1, EN2, EN3 : in std_logic;
7 AD0, AD1, AD2, AD3 : in std_logic_vector (4 downto 0);
8 DI0, DI1, DI2, DI3 : in std_logic_vector (7 downto 0);
9 DIR_RAM : in std_logic_vector (1 downto 0);
10 DO : out std_logic_vector (31 downto 0));
11 end MEM_RAM;
12
13 architecture BEH of MEM_RAM is
14
15 type RAM_TYPE is array (0 to 31) of std_logic_vector(7 downto
0);
16 signal RAM0 : RAM_TYPE := (
17 X"AA",others => (others => '0'));
18 signal RAM1 : RAM_TYPE := (
19 X"BB",others => (others => '0'));
20 signal RAM2 : RAM_TYPE := (
21 X"CC",others => (others => '0'));
22 signal RAM3 : RAM_TYPE := (
23 X"DD",others => (others => '0'));
24
25 attribute RAM_STYLE : string;
Annexos
112
26 attribute RAM_STYLE of RAM0 : signal is "block";
27 attribute RAM_STYLE of RAM1 : signal is "block";
28 attribute RAM_STYLE of RAM2 : signal is "block";
29 attribute RAM_STYLE of RAM3 : signal is "block";
30
31 signal DO0, DO1, DO2, DO3 : std_logic_vector (7 downto 0);
32
33 begin
34
35 process(CLK)
36 begin
37 if rising_edge(CLK) then
38
39 -- RAM 0
40 if (EN0 = '1') then
41 if (WE = '1') then
42 RAM0(to_integer(unsigned(AD0))) <= DI0;
43 end if;
44 if (CLR = '1') then
45 DO0 <= (others => '0');
46 else
47 DO0 <= RAM0(to_integer(unsigned(AD0)));
48 end if;
49 end if;
50
51 -- RAM 1
52 if (EN1 = '1') then
53 if (WE = '1') then
54 RAM1(to_integer(unsigned(AD1))) <= DI1;
55 end if;
56 if (CLR = '1') then
57 DO1 <= (others => '0');
58 else
59 DO1 <= RAM1(to_integer(unsigned(AD1)));
60 end if;
61 end if;
62
63 -- RAM 2
64 if (EN2 = '1') then
65 if (WE = '1') then
66 RAM2(to_integer(unsigned(AD2))) <= DI2;
67 end if;
68 if (CLR = '1') then
69 DO2 <= (others => '0');
70 else
71 DO2 <= RAM2(to_integer(unsigned(AD2)));
72 end if;
73 end if;
74
75 -- RAM 3
76 if (EN3 = '1') then
77 if (WE = '1') then
Diseño, implementación y test de un core RISC-V
113
78 RAM3(to_integer(unsigned(AD3))) <= DI3;
79 end if;
80 if (CLR = '1') then
81 DO3 <= (others => '0');
82 else
83 DO3 <= RAM3(to_integer(unsigned(AD3)));
84 end if;
85 end if;
86
87 end if;
88 end process;
89
90 with DIR_RAM select
91 DO <= DO3&DO2&DO1&DO0 when "00",
92 DO0&DO3&DO2&DO1 when "01",
93 DO1&DO0&DO3&DO2 when "10",
94 DO2&DO1&DO0&DO3 when others;
95
96 end BEH;
B.12 ROM
1 library IEEE;
2 use IEEE.STD_LOGIC_1164.all;
3 use IEEE.NUMERIC_STD.all;
4
5 entity MEM_ROM is
6 port( CLK, EN : in std_logic;
7 AD : in std_logic_vector(4 downto 0);
8 DO : out std_logic_vector(31 downto 0));
9 end MEM_ROM;
10
11 architecture BEH of MEM_ROM is
12
13 type ROM_TYPE is array (0 to 31) of std_logic_vector(7 downto
0);
14 signal ROM0 : ROM_TYPE := (
15 X"A3",X"A3",X"A3",X"A3",
16 others => (others => '0'));
17
18 signal ROM1 : ROM_TYPE := (
19 X"03",X"15",X"27",X"92",
20 others => (others => '0'));
21
22 signal ROM2 : ROM_TYPE := (
23 X"94",X"94",X"94",X"E6",
24 others => (others => '0'));
25
26 signal ROM3 : ROM_TYPE := (
27 X"02",X"02",X"02",X"04",
28 others => (others => '0'));
Annexos
114
29
30 attribute ROM_STYLE : string;
31 attribute ROM_STYLE of ROM0 : signal is "block";
32 attribute ROM_STYLE of ROM1 : signal is "block";
33 attribute ROM_STYLE of ROM2 : signal is "block";
34 attribute ROM_STYLE of ROM3 : signal is "block";
35
36 begin
37
38 process(CLK)
39 begin
40 if rising_edge(CLK) then
41 if (EN = '1') then
42 DO <=
ROM3(to_integer(unsigned(AD)))&ROM2(to_integer(unsigned(AD)))&ROM1
(
to_integer(unsigned(AD)))&ROM0(to_integer(unsigned(AD)));
43 end if;
44 end if;
45 end process;
46
47 end BEH;
B.13 Mux_4x1
1 library IEEE;
2 use IEEE.STD_LOGIC_1164.all;
3
4 entity MUX_4X1 is
5 port( I0,I1,I2,I3 : in std_logic_vector (31 downto 0);
6 S : in std_logic_vector (1 downto 0);
7 O : out std_logic_vector (31 downto 0));
8 end MUX_4X1;
9
10 architecture DATAFLOW of MUX_4X1 is
11 begin
12
13 with S select
14 O <= I0 when "00",
15 I1 when "01",
16 I2 when "10",
17 I3 when others;
18
19 end DATAFLOW;
Diseño, implementación y test de un core RISC-V
115
B.14 Mux2x1_9b
1 library IEEE;
2 use IEEE.STD_LOGIC_1164.all;
3
4 entity MUX2X1_9B is
5 port( I0,I1 : in std_logic_vector (8 downto 0);
6 S : in std_logic;
7 O : out std_logic_vector (8 downto 0));
8 end MUX2X1_9B;
9
10 architecture DATAFLOW of MUX2X1_9B is
11 begin
12
13 with S select
14 O <= I0 when '0',
15 I1 when others;
16
17 end DATAFLOW;
B.15 Mux2x1_32b
1 library IEEE;
2 use IEEE.STD_LOGIC_1164.all;
3
4 entity MUX2X1_32B is
5 port( I0,I1 : in std_logic_vector (31 downto 0);
6 S : in std_logic;
7 O : out std_logic_vector (31 downto 0));
8 end MUX2X1_32B;
9
10 architecture DATAFLOW of MUX2X1_32B is
11 begin
12
13 with S select
14 O <= I0 when '0',
15 I1 when others;
16
17 end DATAFLOW;
B.16 Reg_Bank
1 library IEEE;
2 use IEEE.STD_LOGIC_1164.all;
3 use IEEE.NUMERIC_STD.all;
4
5 entity REG_BANK is
6 port( CLK,WE : in std_logic;
7 A1,A2,AD : in std_logic_vector (4 downto 0);
8 DI : in std_logic_vector (31 downto 0);
Annexos
116
9 DO1,DO2 : out std_logic_vector (31 downto 0));
10 end REG_BANK;
11
12 architecture BEH of REG_BANK is
13
14 type REG_TYPE is array (31 downto 0) of std_logic_vector (31
downto 0);
15 signal REG : REG_TYPE := (
16 X"00000000", X"00000000", X"00000000", X"00000000",
X"00000000", X"00000000",
X"00000000", X"00000000",
17 X"00000000", X"00000000", X"00000015", X"FE000000",
X"00000013", X"00000012",
X"00000011", X"FFFFFF10",
18 X"0000000F", X"0000ABCD", X"000000BD", X"0000000C",
X"0000000A", X"00000001",
X"E718FFAA", X"0000005D",
19 X"00000007", X"00000006", X"00000005", X"FFFFFFFA",
X"00000003", X"00000002",
X"00000001", X"00000000");
20
21 begin
22
23 process(CLK)
24 begin
25 if rising_edge(CLK)then
26 if (WE = '1' and AD /= "00000") then
27 REG(to_integer(unsigned(AD))) <= DI;
28 end if;
29 end if;
30 end process;
31
32 DO1 <= REG(to_integer(unsigned(A1)));
33 DO2 <= REG(to_integer(unsigned(A2)));
34
35 end BEH;
Diseño, implementación y test de un core RISC-V
117
B.17 Registro_9b
1 library IEEE;
2 use IEEE.STD_LOGIC_1164.all;
3
4 entity REGISTRO9 is
5 port( CLK, CLR, EN : in std_logic;
6 R9_IN : in std_logic_vector (8 downto 0);
7 R9_OUT : out std_logic_vector (8 downto 0));
8 end REGISTRO9;
9
10 architecture BEH of REGISTRO9 is
11 begin
12
13 process (CLK)
14 begin
15 if rising_edge(CLK) then
16 if (CLR = '1') then
17 R9_OUT <= (others => '0');
18 elsif (EN = '1') then
19 R9_OUT <= R9_IN;
20 end if;
21 end if;
22 end process;
23
24 end BEH;
B.18 Out Nexys 2
1 library IEEE;
2 use IEEE.STD_LOGIC_1164.all;
3 use IEEE.STD_LOGIC_UNSIGNED.all;
4
5 entity OUT_NEXYS2 is
6 port( CLK,RST : in std_logic;
7 P_OUT : in std_logic_vector (15 downto 0);
8 AN : out std_logic_vector (3 downto 0);
9 SSEG : out std_logic_vector (7 downto 0));
10 end OUT_NEXYS2;
11
12 architecture BEH of OUT_NEXYS2 is
13 signal COMPT_PR : integer := 0;
14 signal CONTADOR_4 : std_logic_vector (1 downto 0);
15 signal MUX_OUT : std_logic_vector (3 downto 0);
16
17 begin
18
19 -- Prescaler y contador modulo 4
20
21 process (CLK)
22 begin
Annexos
118
23 if rising_edge(CLK) then
24 if (RST = '1') then
25 COMPT_PR <= 0;
26 CONTADOR_4 <= (others => '0');
27 else
28 if (COMPT_PR = 49999) then
29 COMPT_PR <= 0;
30 CONTADOR_4 <= CONTADOR_4 + 1;
31 else
32 COMPT_PR <= COMPT_PR + 1;
33 end if;
34 end if;
35 end if;
36 end process;
37
38 -- MUX
39
40 with CONTADOR_4 select
41 MUX_OUT <= P_OUT (3 downto 0) when "00",
42 P_OUT (7 downto 4) when "01",
43 P_OUT (11 downto 8) when "10",
44 P_OUT (15 downto 12) when others;
45
46 -- Decoder 2x4
47
48 with CONTADOR_4 select
49 AN <= "1110" when "00",
50 "1101" when "01",
51 "1011" when "10",
52 "0111" when others;
53
54 -- Decoder Hx - SSEG
55
56 with MUX_OUT select 57 SSEG <= "00000011" when "0000", -- 0
58 "10011111" when "0001", -- 1
59 "00100101" when "0010", -- 2
60 "00001101" when "0011", -- 3
61 "10011001" when "0100", -- 4
62 "01001001" when "0101", -- 5
63 "01000001" when "0110", -- 6
64 "00011111" when "0111", -- 7
65 "00000001" when "1000", -- 8
66 "00001001" when "1001", -- 9
67 "00010001" when "1010", -- A
68 "11000001" when "1011", -- B
69 "01100011" when "1100", -- C
70 "10000101" when "1101", -- D
71 "01100001" when "1110", -- E
72 "01110001" when "1111", -- F
73 "11111111" when others;
74
Diseño, implementación y test de un core RISC-V
119
75 end BEH;
B.19 Registro_32b
1 library IEEE;
2 use IEEE.STD_LOGIC_1164.all;
3
4 entity REGISTRO32 is
5 port( CLK, CLR, EN : in std_logic;
6 R32_IN : in std_logic_vector (31 downto 0);
7 R32_OUT : out std_logic_vector (31 downto 0));
8 end REGISTRO32;
9
10 architecture BEH of REGISTRO32 is
11 begin
12
13 process (CLK)
14 begin
15 if rising_edge(CLK) then
16 if (CLR = '1') then
17 R32_OUT <= (others => '0');
18 elsif (EN = '1') then
19 R32_OUT <= R32_IN;
20 end if;
21 end if;
22 end process;
23
24 end BEH;
B.20 UC
1 library IEEE;
2 use IEEE.STD_LOGIC_1164.all;
3
4 entity UC is
5 port( CLK,RST,FZ : in std_logic;
6 FUNCT3 : in std_logic_vector (2 downto 0);
7 FUNCT7,OPCODE : in std_logic_vector (6 downto 0);
8 EPC,I_D,EMW,D_SIGN,ERWR,A_SEL,EAR,EBR : out std_logic;
9 MWLE,ISBJ_SEL,D_LENGHT,DI_SEL,B_SEL : out
std_logic_vector (1 downto 0);
10 ALU_CONTROL : out std_logic_vector (3 downto 0));
11 end UC;
12
13 architecture BEH of UC is
14
15 type ESTADO_T is
(S0,S1,S2,S3,S4,S5,S6,S7,S8,S9,S10,S11,S12,S13,S14,S15,S16,S17,S18
,
S19,S20,S21,S22,S23,S24,S25,S26,S27,S28,S29,S30,S31,S32,S33,S34);
16 signal ESTADO : ESTADO_T;
Annexos
120
17
18 signal ALU_CODE : std_logic_vector (3 downto 0);
19
20 begin
21
22
23 ALU_CODE <= FUNCT3&FUNCT7(5);
24
25 process(CLK)
26 begin
27 if rising_edge(CLK) then
28 if (RST = '1') then
29 ESTADO <= S0;
30 else
31 case ESTADO is
32 when S0 =>
33 ESTADO <= S1;
34 when S1 =>
35 if (OPCODE = "0110011") then
36 ESTADO <= S2;
37 elsif (OPCODE = "0010011" or OPCODE =
"0000011") then
38 ESTADO <= S13;
39 elsif (OPCODE = "0110111") then
40 ESTADO <= S14;
41 elsif (OPCODE = "0010111") then
42 ESTADO <= S15;
43 elsif (OPCODE = "1101111") then
44 ESTADO <= S16;
45 elsif (OPCODE = "1100111") then
46 ESTADO <= S17;
47 elsif (OPCODE = "1100011") then
48 ESTADO <= S19;
49 elsif (OPCODE = "0100011") then
50 ESTADO <= S31;
51 end if;
52 when S2 =>
53 if (ALU_CODE = "0000") then
54 ESTADO <= S3;
55 elsif (ALU_CODE = "0001") then 56 ESTADO <= S4;
57 elsif (ALU_CODE = "0010") then
58 ESTADO <= S5;
59 elsif (ALU_CODE = "0100") then
60 ESTADO <= S6;
61 elsif (ALU_CODE = "0110") then
62 ESTADO <= S7;
63 elsif (ALU_CODE = "1000") then
64 ESTADO <= S8;
65 elsif (ALU_CODE = "1010") then
66 ESTADO <= S9;
67 elsif (ALU_CODE = "1011") then
Diseño, implementación y test de un core RISC-V
121
68 ESTADO <= S10;
69 elsif (ALU_CODE = "1100") then
70 ESTADO <= S11;
71 elsif (ALU_CODE = "1110") then
72 ESTADO <= S12;
73 end if;
74 when S3 =>
75 ESTADO <= S1;
76 when S4 =>
77 ESTADO <= S1;
78 when S5 =>
79 ESTADO <= S1;
80 when S6 =>
81 ESTADO <= S1;
82 when S7 =>
83 ESTADO <= S1;
84 when S8 =>
85 ESTADO <= S1;
86 when S9 =>
87 ESTADO <= S1;
88 when S10 =>
89 ESTADO <= S1;
90 when S11 =>
91 ESTADO <= S1;
92 when S12 =>
93 ESTADO <= S1;
94 when S13 =>
95 if (OPCODE = "0000011") then
96 ESTADO <= S25;
97 else
98 if (ALU_CODE = "0000") then
99 ESTADO <= S3;
100 elsif (ALU_CODE = "0001") then
101 ESTADO <= S4;
102 elsif (ALU_CODE = "0010") then
103 ESTADO <= S5;
104 elsif (ALU_CODE = "0100") then
105 ESTADO <= S6;
106 elsif (ALU_CODE = "0110") then
107 ESTADO <= S7;
108 elsif (ALU_CODE = "1000") then
109 ESTADO <= S8;
110 elsif (ALU_CODE = "1010") then
111 ESTADO <= S9;
112 elsif (ALU_CODE = "1011") then 113 ESTADO <= S10;
114 elsif (ALU_CODE = "1100") then
115 ESTADO <= S11;
116 elsif (ALU_CODE = "1110") then
117 ESTADO <= S12;
118 end if;
119 end if;
Annexos
122
120 when S14 =>
121 ESTADO <= S0;
122 when S15 =>
123 ESTADO <= S3;
124 when S16 =>
125 ESTADO <= S18;
126 when S17 =>
127 ESTADO <= S18;
128 when S18 =>
129 ESTADO <= S0;
130 when S19 =>
131 if (FUNCT3 = "000" or FUNCT3 = "001")
then
132 ESTADO <= S20;
133 elsif (FUNCT3 = "100" or FUNCT3 =
"101") then
134 ESTADO <= S21;
135 elsif (FUNCT3 = "110" or FUNCT3 =
"111") then
136 ESTADO <= S22;
137 end if;
138 when S20 =>
139 if (FUNCT3 = "000") then --BEQ
140 if (FZ = '1') then
141 ESTADO <= S23;
142 else
143 ESTADO <= S24;
144 end if;
145 else --BNE
146 if (FZ = '1') then
147 ESTADO <= S24;
148 else
149 ESTADO <= S23;
150 end if;
151 end if;
152 when S21 =>
153 if (FUNCT3 = "100") then --BLT
154 if (FZ = '1') then
155 ESTADO <= S24;
156 else
157 ESTADO <= S23;
158 end if;
159 else --BGE
160 if (FZ = '1') then
161 ESTADO <= S23;
162 else
163 ESTADO <= S24;
164 end if;
165 end if;
166 when S22 =>
167 if (FUNCT3 = "110") then --BLTU
168 if (FZ = '1') then
Diseño, implementación y test de un core RISC-V
123
169 ESTADO <= S24; 170 else
171 ESTADO <= S23;
172 end if;
173 else --BGEU
174 if (FZ = '1') then
175 ESTADO <= S23;
176 else
177 ESTADO <= S24;
178 end if;
179 end if;
180 when S23 =>
181 ESTADO <= S18;
182 when S24 =>
183 ESTADO <= S18;
184 when S25 =>
185 if (FUNCT3 = "000") then
186 ESTADO <= S26; --LB
187 elsif (FUNCT3 = "001") then
188 ESTADO <= S27; --LH
189 elsif (FUNCT3 = "010") then
190 ESTADO <= S28; --LW
191 elsif (FUNCT3 = "100") then
192 ESTADO <= S29; --LBU
193 elsif (FUNCT3 = "101") then
194 ESTADO <= S30; --LHU
195 end if;
196 when S26 =>
197 ESTADO <= S1;
198 when S27 =>
199 ESTADO <= S1;
200 when S28 =>
201 ESTADO <= S1;
202 when S29 =>
203 ESTADO <= S1;
204 when S30 =>
205 ESTADO <= S1;
206 when S31 =>
207 if (FUNCT3 = "000") then
208 ESTADO <= S32; --SB
209 elsif (FUNCT3 = "001") then
210 ESTADO <= S33; --SH
211 elsif (FUNCT3 = "010") then
212 ESTADO <= S34; --SW
213 end if;
214 when S32 =>
215 ESTADO <= S0;
216 when S33 =>
217 ESTADO <= S0;
218 when S34 =>
219 ESTADO <= S0;
220 end case;
Annexos
124
221 end if;
222 end if;
223 end process;
224
225 with ESTADO select
226 EPC <= '1' when S2|S13|S14|S15|S18|S31, 227 '0' when others;
228
229 with ESTADO select
230 I_D <= '1' when S25|S32|S33|S34,
231 '0' when others;
232
233 with ESTADO select
234 EMW <= '1' when S32|S33|S34,
235 '0' when others;
236
237 with ESTADO select
238 MWLE <= "00" when S32,
239 "01" when S33,
240 "10" when others;
241
242 with ESTADO select
243 ISBJ_SEL <= "01" when S31,
244 "10" when S23,
245 "11" when S16,
246 "00" when others;
247
248 with ESTADO select
249 D_SIGN <= '0' when S26|S27,
250 '1' when others;
251
252 with ESTADO select
253 D_LENGHT <= "01" when S27|S30,
254 "10" when S26|S29,
255 "00" when others;
256
257 with ESTADO select
258 DI_SEL <= "00" when S14,
259 "01" when S26|S27|S28|S29|S30,
260 "10" when others;
261
262 with ESTADO select
263 ERWR <= '0' when
S0|S1|S2|S13|S15|S18|S19|S20|S21|S22|S23|S24|S25|S31|S32|S33|S34,
264 '1' when others;
265
266 with ESTADO select
267 A_SEL <= '0' when S1|S23|S24,
268 '1' when others;
269
270 with ESTADO select
271 B_SEL <= "00" when S1|S24,
Diseño, implementación y test de un core RISC-V
125
272 "01" when S15,
273 "11" when S2|S19,
274 "10" when others;
275
276 with ESTADO select
277 EAR <= '1' when S1|S2|S13|S17|S19|S23|S24|S31,
278 '0' when others;
279
280 with ESTADO select
281 EBR <= '1' when S1|S2|S13|S15|S16|S17|S19|S23|S24|S31,
282 '0' when others;
283 284 with ESTADO select
285 ALU_CONTROL <= "0001" when S4,
286 "0010" when S5,
287 "0100" when S6|S21,
288 "0110" when S7|S22,
289 "1000" when S8|S20,
290 "1010" when S9,
291 "1011" when S10,
292 "1100" when S11,
293 "1110" when S12,
294 "0000" when others;
295
296 end BEH;
B.21 tb_TOP
1 library IEEE;
2 use IEEE.std_logic_1164.all;
3
4 entity TB_TOP is
5 end TB_TOP;
6
7 architecture BEH of TB_TOP is
8
9 component TOP is
10 port( CLK_FPGA, RST_FPGA : in std_logic;
11 SW : in std_logic_vector (7 downto 0);
12 AN : out std_logic_vector (3 downto 0);
13 SSEG : out std_logic_vector (7 downto 0));
14 end component;
15
16 signal CLK_FPGA : std_logic := '0';
17 signal RST_FPGA : std_logic := '1';
18 signal SW : std_logic_vector (7 downto 0) := X"CC";
19 signal AN : std_logic_vector (3 downto 0);
20 signal SSEG : std_logic_vector (7 downto 0);
21
22 constant CLK_PERIOD : time := 20 ns;
23
Annexos
126
24 begin
25
26 UUT : TOP port map (CLK_FPGA => CLK_FPGA, RST_FPGA => RST_FPGA,
SW => SW, AN => AN,
SSEG => SSEG);
27
28 CLK_PROCESS : process
29 begin
30 CLK_FPGA <= '1';
31 wait for CLK_PERIOD/2;
32 CLK_FPGA <= '0';
33 wait for CLK_PERIOD/2;
34 end process;
35
36 RST_PROCESS : process
37 begin
38 wait for 5 ns;
39 RST_FPGA <= '0';
40 wait;
41 end process;
42
43 end BEH;
Diseño, implementación y test de un core RISC-V
127
Anexo C. Tabla señales de control de UC
S0 S1 S2 S3 S4 S5
fetch Operandos PC +
4 Operandos R ALU add ALU sub ALU sll
EPC 0 0 1 0 0 0
I_D 0 - - 0 0 0
EMW 0 0 0 0 0 0
MWLe -- -- -- -- -- --
ISBJ_sel -- -- -- -- -- --
D_sign - - - - - -
D_Lenght -- -- -- -- -- --
di_sel -- -- -- 10 10 10
ERWr 0 0 0 1 1 1
A_sel - 0 1 - - -
B_sel -- 00 11 - - -
EAR 0 1 1 0 0 0
EBR 0 1 1 0 0 0
ALUControl ---- ---- 0000 0000 0001 0010
S6 S7 S8 S9 S10 S11
ALU slt ALU sltu ALU xor ALU srl ALU sra ALU or
EPC 0 0 0 0 0 0
I_D 0 0 0 0 0 0
EMW 0 0 0 0 0 0
MWLe -- -- -- -- -- --
ISBJ_sel -- -- -- -- -- --
D_sign - - - - - -
D_Lenght -- -- -- -- -- --
di_sel 10 10 10 10 10 10
ERWr 1 1 1 1 1 1
A_sel - - - - - -
B_sel - - - - - -
EAR 0 0 0 0 0 0
EBR 0 0 0 0 0 0
ALUControl 0100 0110 1000 1010 1011 1100
Annexos
128
S12 S13 S14 S15 S16 S17
ALU and Operandos
I LUI
Operandos AUIPC
Operandos J Operandos
JALR
EPC 0 1 1 1 0 0
I_D 0 - - - - -
EMW 0 0 0 0 0 0
MWLe -- -- -- -- -- --
ISBJ_sel -- 00 -- -- 11 --
D_sign - - - - - -
D_Lenght -- -- -- -- -- --
di_sel 10 -- 00 -- 10 10
ERWr 1 0 1 0 1 1
A_sel - 1 - - - 1
B_sel - 10 -- 01 10 10
EAR 0 1 0 0 0 1
EBR 0 1 0 1 1 1
ALUControl 1110 0000 0000 0000 0000 0000
S18 S19 S20 S21 S22 S23
Salto Operandos
B Comparación
igual que
Comparación mayor o menor
Comparación sin signo
Cumple (salto)
EPC 1 0 0 0 0 0
I_D - - - - - -
EMW 0 0 0 0 0 0
MWLe -- -- -- -- -- --
ISBJ_sel -- -- -- -- -- 10
D_sign - - - - - -
D_Lenght -- -- -- -- -- --
di_sel -- -- -- -- -- --
ERWr 0 0 0 0 0 0
A_sel - 1 - - - 0
B_sel -- 11 -- -- -- 10
EAR 0 1 0 0 0 1
EBR 0 1 0 0 0 1
ALUControl 0000 ---- 1000 0100 0110 ----
Diseño, implementación y test de un core RISC-V
129
S24 S25 S26 S27 S28 S29
No cumple
(fetch) Lectura Mem
Cargar byte
Cargar media
palabra
Cargar palabra
Cargar byte sin
signo
EPC 0 0 0 0 0 0
I_D - 1 0 0 0 0
EMW 0 0 0 0 0 0
MWLe -- -- -- -- -- --
ISBJ_sel -- -- -- -- -- --
D_sign - - 0 0 - 1
D_Lenght -- -- 10 01 00 10
di_sel -- -- 01 01 01 01
ERWr 0 0 1 1 1 1
A_sel 0 - - - - -
B_sel 00 -- -- -- -- --
EAR 1 0 0 0 0 0
EBR 1 0 0 0 0 0
ALUControl ---- 0000 ---- ---- ---- ----
S30 S31 S32 S33 S34
Cargar media
palabra sin signo
Operandos S Almacenar
byte Almacenar
media palabra Almacenar
palabra
EPC 0 1 0 0 0
I_D 0 - 1 1 1
EMW 0 0 1 1 1
MWLe -- -- 00 01 10
ISBJ_sel -- 01 -- -- --
D_sign 1 - - - -
D_Lenght 01 -- -- -- --
di_sel 01 -- -- -- --
ERWr 1 0 0 0 0
A_sel - 1 - - -
B_sel -- 10 -- -- --
EAR 0 1 0 0 0
EBR 0 1 0 0 0
ALUControl ---- 0000 0000 0000 0000
Diseño, implementación y test de un core RISC-V
133
Anexo E. Fichero de restricciones ISE 1 # clock pin for Nexys 2 Board
2 NET "clk_fpga" LOC = "B8"; # Bank = 0, Pin name =
IP_L13P_0/GCLK8, Type = GCLK, Sch name = GCLK0
3
4 # 7 segment display
5 NET "sseg<7>" LOC = "L18"; # Bank = 1, Pin name = IO_L10P_1,
Type = I/O, Sch name = CA
6 NET "sseg<6>" LOC = "F18"; # Bank = 1, Pin name = IO_L19P_1,
Type = I/O, Sch name = CB
7 NET "sseg<5>" LOC = "D17"; # Bank = 1, Pin name = IO_L23P_1/HDC,
Type = DUAL, Sch name = CC
8 NET "sseg<4>" LOC = "D16"; # Bank = 1, Pin name =
IO_L23N_1/LDC0, Type = DUAL, Sch name = CD
9 NET "sseg<3>" LOC = "G14"; # Bank = 1, Pin name = IO_L20P_1,
Type = I/O, Sch name = CE
10 NET "sseg<2>" LOC = "J17"; # Bank = 1, Pin name =
IO_L13P_1/A6/RHCLK4/IRDY1, Type =
RHCLK/DUAL, Sch name = CF
11 NET "sseg<1>" LOC = "H14"; # Bank = 1, Pin name = IO_L17P_1,
Type = I/O, Sch name = CG
12 NET "sseg<0>" LOC = "C17"; # Bank = 1, Pin name =
IO_L24N_1/LDC2, Type = DUAL, Sch name = DP
13
14 NET "an<0>" LOC = "F17"; # Bank = 1, Pin name = IO_L19N_1, Type
= I/O, Sch name = AN0
15 NET "an<1>" LOC = "H17"; # Bank = 1, Pin name = IO_L16N_1/A0,
Type = DUAL, Sch name = AN1
16 NET "an<2>" LOC = "C18"; # Bank = 1, Pin name = IO_L24P_1/LDC1,
Type = DUAL, Sch name
= AN2
17 NET "an<3>" LOC = "F15"; # Bank = 1, Pin name = IO_L21P_1, Type
= I/O, Sch name = AN3
18
19 # Switches
20 NET "sw<0>" LOC = "G18"; # Bank = 1, Pin name = IP, Type =
INPUT, Sch name = SW0
21 NET "sw<1>" LOC = "H18"; # Bank = 1, Pin name = IP/VREF_1, Type
= VREF, Sch name = SW1
22 NET "sw<2>" LOC = "K18"; # Bank = 1, Pin name = IP, Type =
INPUT, Sch name = SW2
23 NET "sw<3>" LOC = "K17"; # Bank = 1, Pin name = IP, Type =
INPUT, Sch name = SW3
24 NET "sw<4>" LOC = "L14"; # Bank = 1, Pin name = IP, Type =
INPUT, Sch name = SW4
25 NET "sw<5>" LOC = "L13"; # Bank = 1, Pin name = IP, Type =
INPUT, Sch name = SW5
26 NET "sw<6>" LOC = "N17"; # Bank = 1, Pin name = IP, Type =
INPUT, Sch name = SW6
27 NET "sw<7>" LOC = "R17"; # Bank = 1, Pin name = IP, Type =
INPUT, Sch name = SW7