DISEÑO, IMPLEMENTACIÓN Y TEST DE UN CORE RISC-V

152
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

Transcript of DISEÑO, IMPLEMENTACIÓN Y TEST DE UN CORE RISC-V

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.

Memoria

iv

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

Memoria

xiv

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.

Memoria

78

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.

Memoria

84

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.

Memoria

86

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.

Memoria

90

Diseño, implementación y test de un core RISC-V

91

Anexo A. Datapath

Annexos

92

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

Annexos

130

Diseño, implementación y test de un core RISC-V

131

Anexo D. Codificación instrucciones RV32I

Annexos

132

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

Annexos

134

28

29 # Buttons

30

31 NET "rst_fpga" LOC = "B18"; # Bank = 1, Pin name = IP, Type =

INPUT, Sch name = BTN0