Desarrollo de software en equipo (Parte 2)


Tecnología

Es muy importante que tu equipo tenga claro el alcance y el matiz tecnológico que le darás a tu proyecto. Como seguramente ya sabes, no es lo mismo hacer una aplicación web que una aplicación de escritorio, o un videojuego 3D, o un sistema con backend al que se conectan múltiples tipos de aplicación cliente.

En el pasado quizá hubiese sido dicho, que es mas importante una gestión cuidadosa que una maleta llena de herramientas. Sin embargo, cada día las herramientas existentes nos dan un grado de poder y un alcance enorme. Piense como hubiese sido escrito su aplicación web favorita hace 7 años, cuando no existía AngularJS, Knockout.JS o (ponga aquí su framework favorito). Hubiese podido entregarse el mismo nivel de funcionalidad al mismo costo de hoy en día?

A continuación le daré un breve resumen de lo que la tecnología le puede entregar en el ahora y una recomendación sobre sus elecciones.

Lenguajes de programación

La promesa de la tecnología en este ámbito es interesante, los lenguajes de programación de ultima generación intentan ofrecer una elevada densidad de funcionalidad por linea de código escrita y un grado de abstracción aun mayor. Los mas modernos lenguajes intentan ser tan declarativos como sea posible con el fin de que el programador pueda expresar sus deseos sin ocuparse de los algoritmos para alcanzar esta expectativa.

“Los lenguajes declarativos de ultima generación … aun se enfrentan a un desafío técnico no superado”

languagesEl paradigma de programación declarativa estriba también en que construir software es realidad escribir una especificación para la cual una implementación será hecha de forma automática, por lo tanto la tarea de testing se hace innecesaria ya que es un supuesto que la implementación automática cumplirá los requerimientos de la especificación. Sin embargo estos lenguajes se enfrentan a un desafío técnico aun no superado, las descripciones declarativas aún son auto-implementadas de maneras suboptimas, por lo que se encuentran fuera de los fines prácticos o cuando mucho limitadas a casos donde el desempeño no es de gran importancia. Por esta y otras razones no seria una decisión sabia seleccionar esta tecnología para emprender tu nuevo proyecto de desarrollo.

Para emprender tu nuevo proyecto de desarrollo de software comercial la oferta tecnología es mucho mas madura fundamentalmente en dos ámbitos, lenguajes de programación imperativos con tipado estático y lenguajes de programación imperativos con tipado dinámico. En el primer grupo puedes encontrar lenguajes populares como C#, Java, Go y C++, y son mi recomendación para el desarrollo de proyectos de software en equipo y con enfoque de ingeniería claro.

La razón para escoger este grupo estriba en que el sistema de tipos fuerte le permite al arquitecto y los desarrolladores interactuar sobre garantías locales claras. Es decir, que el arquitecto puede fácilmente describir interfaces explicitamente en el código fuente que luego los desarrolladores implementarán. Así podrán ponerse de acuerdo con un menor esfuerzo debido a que el código fuente al contener explicitamente las interfaces puede ser auto descriptivo, luego las herramientas de compilación validarán una buena parte de las restricciones que impuso el arquitecto al hacer el diseño.

Por otro lado, los lenguajes de programación dinámicos no validarán las restricciones del diseño durante la fase de compilación por lo cual los detalles de la implementación deberán ser revisados con mayor atención de manera manual para verificar la correctitud de la implementación. La arquitectura por su parte no podrá ser inferida desde el código fuente, por lo que un esfuerzo de documentación será requerido por separado. Adicionalmente, las herramientas de compilación al tener garantías locales generan software mas eficiente ya que explota mas propiedades del código fuente escrito que las que podrían ser inferidas desde el código fuente escrito en un lenguaje dinámico.

Tendencias actuales

Entre las tendencias del desarrollo actual que seguro debes implementar se encuentran:

Control de versiones o Control de código fuente

logo_gitEs un mecanismo que le permite al equipo gestionar y compartir cambios de código fuente. Para tu próximo proyecto la recomendación es usar GIT, un sistema poderosamente flexible a la hora de gestionar el código fuente. La ventaja al usar git es que la evolución del código fuente se puede representar como un árbol donde cada nodo representa una versión del software, pero su gestión le permite fácilmente manejar las bifurcaciones y mantener muy baja la sobrecarga de gestionar diferentes sub desarrollos a la vez.

Automatización de pruebas

Debido a que la verificación automática de programas aun es una promesa lejana y materia de fuerte investigación actual, nuestro software deberá ser probado para poder asegurar que su implementación es correcta. La mejor opción para equipos de desarrollo es escribir pruebas usando el propio código fuente que son ejecutadas automáticamente en cualquier momento para corroborar que un desarrollo nuevo no daña funcionalidad escrita previamente.
coverage_with_resharperComo regla general haga que el equipo implemente pruebas que recorran cada linea de código ejecutable en el sistema, luego deje que una herramienta automática le reporte el porcentaje de código que fue cubierto en dicha ejecución ademas de la cantidad de casos de prueba que fueron exitosos y fallidos. Asegure que su equipo mantiene un nivel de cobertura cercano al 100% y la totalidad de las pruebas resulten exitosas.

Análisis estático de código

static_analysisConsiste en tomar una herramienta automática que revisa el código fuente verificando que un sin numero de reglas están satisfechas. Estas reglas suelen ser configurables y mas blandas que las reglas propias del lenguaje de programación, es decir, el análisis estático no te dirá si un programa es o no correcto desde el punto de vista de como esta escrito, pero puede indicarte usos del lenguaje que aunque son legales para el compilador no son recomendados o no satisfacen restricciones de diseño impuestas para las necesidades particulares del proyecto, o van en contravia de las mejores practicas reconocidas. Entre las mejores herramientas en este rubro tenemos Resharper, StyleCop y SonarQube

Aplicación de formato y estilo automático al código fuente.

Consiste en aplicar un conjunto de reglas que hacen que el código fuente luzca siempre con el mismo estilo de codificación a pesar de que lo escriban personas diferentes.

js_code_style_indentsSuelen tener muchas opciones de configuración, establezca opciones que siempre favorezcan la legibilidad y la productividad de quien lee el código. Por ejemplo, ajuste un numero máximo de caracteres por linea de manera que quien lee el código no requiera hacer scroll en su propia pantalla, este valor solía ser 80 pero debido a las resoluciones que manejamos hoy en día en nuestras pantallas un valor de 140 a 160 es mucho mas útil. Establezca opciones que le permita maximizar la densidad de código siempre que este mantenga legible pero armonice con sus preferencias estéticas.  Instruya a su equipo en bajo la premisa de que el código se escribe primero para las personas y luego para el compilador. Entre las herramientas mas destacadas para este propósito tenemos Resharper, Visual Assist, IntelliJ IDEA

“el código se escribe primero para las personas y luego para el compilador”

Gestión automática de dependencias

nugetsActualmente existen los muy populares gestores de paquetes que le permiten administrar con mucha facilidad las bibliotecas de las cuales nuestro proyecto depende, estas son herramientas que hacen fácil nuestro proceso de construcción y entrega ya que nos permiten mantener actualizadas nuestras dependencias y facilitan la migración cuando deseamos hacer cambios en los paquetes que deseamos usar. Entre las herramientas mas reconocidas de este campo tenemos: Maven, Nuget, ChocolateyIvy, npm, bower, pip

Calidades de construcción

delphi-project-manager-debug-releaseCuando compilas y construyes tu proyecto puedes hacerlo con diferentes fines. Por ejemplo, con el fin de ejecutar pruebas manuales o depurar la ejecución de las ultimas lineas de código que escribiste, o entregarlo al cliente para uso en producción. Debido a esto el resultado de la construcción puede requerir ser configurado con diferentes opciones por ejemplo pedir al compilador el máximo nivel de optimización para una ejecución mas eficiente y de alto desempeño, o por el contrario favorecer la experiencia de depuración, o por ejemplo habilitar la optimización pero dejar también habilitada la comprobación de aserciones de código.
Es muy importante que el arquitecto y al menos un desarrollador senior conozca a fondo las posibilidades que la tecnología de construcción le brinda para tomar el máximo provecho de esta y conseguir los resultados esperados en menor tiempo.

Integración continua y Entrega continua

Es también el santo grial del desarrollo ágil moderno, cuando te encuentras trabajando en equipo, sabes que el trabajo que cada miembro hace al final deberá integrarse al software, pero con un poco de experiencia te das cuenta que puede convertirse en un dolor de cabeza debido a que el trabajo de los miembros puede fácilmente sobrelaparse con el de otros, este problema esta parcialmente resuelto con las herramientas control de código fuente o de control de versiones que vimos mas arriba.

Sin embargo, aunque puedas combinar fácilmente el código de tu equipo en un solo repositorio, no esta garantizado que el código integrado funcionará correctamente estando junto, o peor aun, no dañará funcionalidad escrita con anterioridad. En calidad de agravante, el día que decidas tomar tu software y prepararlo para la entrega al cliente o a la puesta en producción encontraras que algunos aspectos adicionales deben ser solventados, por ejemplo, la creación de un instalador, poblar bases de datos con información semilla, inicializar servicios web, realizar cierta preparación de la maquina anfitriona, etc. Puedes ver que fácilmente esto puede convertirse en un dolor de cabeza que a menudo debe repetirse con cada release. Entonces, aparece el concepto de integración continua, el cual busca integrar automáticamente todos los aspectos del software de manera que en todo momento puedas contar con una versión del software listo para entregar, así podrás conocer y atender tempranamente los problemas propios de la integración y disminuir dramáticamente el riesgo de la entrega.

jenkins-plugin-diagram-saciA las herramientas de integración continua puedes agregar la automatización de muchas de las otras herramientas aquí mencionadas. Entonces el flujo de la integración continua quedaría mas o menos así: cuando un desarrollador somete su trabajo al servidor de control de versiones, el sistema de integración continua obtiene la ultima versión del código fuente obtiene los paquetes de los cuales depende el software, construye el software en las calidades para las cuales haya sido configurado y ejecuta las herramientas de análisis de código y ejecuta las pruebas automáticas. Como resultado de esto el director del proyecto puede ver el estado del desarrollo a medida que los desarrolladores realizan cada contribución. Este reporte puede incluir en forma gráfica la cantidad de código fuente que esta siendo probado en la integración continua a partir del porcentaje de cobertura de las pruebas automáticas y la cantidad de pruebas fallidas o exitosas.

teamcity-383469-1279080600

Algunos sistemas de integración continua permiten configurar un dashboard con métricas adicionales que le permiten al director de proyecto tener una visión mas amplia del estado del mismo.

Entre los servidores de integración continua mas conocidos tenemos: Jenkins, Team Foundation Server, TeamCity, AppVeyor, Travis-CI

Desarrollo de software en Equipo (Parte 1)


He decidido escribir este post con el fin de proveer una pequeña guía que exponga las mejores practicas que he encontrado en mis años de experiencia desarrollando software de clase empresarial.Si estas a punto de empezar un desarrollo o te encuentras dirigiendo un proyecto de desarrollo maduro, estas recomendaciones pueden ser de gran ayuda.

Para desarrollar software en equipo de manera exitosa es necesario tener en cuenta un sin numero de aspectos que dan cuenta de lo complejo que puede ser esta actividad, aquí me enfocare en tres rubros fundamentales: Metodología, Tecnología y Arquitectura.

Metodología

Adopta una metodología, abrázala fuerte y se fiel a esta. scrum-kanban-scrumban-intland-softwareSi desarrollas software comercial que no es de misión critica (donde fallas en el sistema nunca pondrían en riesgo el integridad de una persona) mi recomendación casi inmediata es Scrum y Agile, tambien llamadas metodologías ágiles. Esta metodologías te dirán con exactitud que hacer para sacar adelante un proyecto de desarrollo de software. Ademas utiliza las herramientas  visuales de gestión de equipos que adoptan estas metodologías, por ejemplo puedes usar Team Foundation Server (TFS) or JIRA para apalancar tu equipo si usas Scrum.

tfs_IC810589
TFS screenshot – Miembros del equipo asignados a alguna tarea

Una vez adoptas una metodología, cualquiera que sea, es casi un hecho que el concepto de REQUERIMIENTO llegará a tu vida. La mayoría de metodologías de desarrollo de software concuerdan en ahondar en las necesidades del usuario final y extraer de este las funcionalidades que son un requisito para que se sienta a gusto con el software que al final le entreguemos.

Desarrollar software es un proceso de ingeniería a menudo llevado a cabo por personas de muy variadas profesiones, sin embargo suelen ser ingenieros quienes mayoritariamente toman las riendas de los proyectos de desarrollo y a menudo su formación característica los lleva a cometer un error grave. Es muy común que los ingenieros tengan un visión que intenta “asegurar” el éxito del desarrollo desde la plena confianza en una metodología.

“En la practica satisfacer requerimientos no siempre es sinónimo de satisfacer al cliente”

Pero ten mucho cuidado, ya que en la practica satisfacer requerimientos no siempre es sinónimo de satisfacer al cliente. En un mundo ideal, tu le preguntarías al cliente que desea y el te dirá sus requerimientos, al cabo de un tiempo satisfaces esos requerimientos y el cliente no tendría razón para sentir frustración alguna. En la realidad, la amplia mayoría de oportunidades encontraras que tu cliente realmente no sabe lo quiere a pesar de que responda tus preguntas asertivamente. Es mas, puedes apostar a que cambiará de opinión en varias ocasiones. Esto es completamente natural ya que toda organización esta en ultimas formada por seres humanos a lo cual debe sumarse que tu cliente contrata tu equipo para realizar el desarrollo precisamente porque no tiene la habilidad o el conocimiento para desarrollar el software por si mismo, así que es apenas evidente que no tendrá claras una gran cantidad de cosas. Seguramente te preguntarás, entonces cómo es posible satisfacer al cliente? Esta respuesta vale oro y te la diré sin rodeos:

“Acércate a tu cliente, … busca entender qué lo hace o qué lo haría exitoso en su propio juego”

Primero: Acércate al cliente, conoce tanto como puedas de su actividad, entiende su negocio y busca entender que aspectos lo hacen o lo harían exitoso en su propio juego. suena fácil, verdad? pues no lo es. Cuando digo que entiendas qué lo hace exitoso, me refiero a que genuinamente comprendas por qué sus propios clientes lo buscarían y por qué ellos se quedarían con el, en caso de ser posible entiende de donde proviene su mayor beneficio financiero. No seas tímido, si lo deseas puedes ser transparente en todos estos objetivos. Asume a tu cliente como tu socio, solo entonces entenderás que necesita tu cliente.

“Pregúntale a tu cliente lo que quiere pero no esperes su respuesta”

Segundo: Pregúntale a tu cliente lo que quiere pero no esperes su respuesta. Invierte por adelantado parte de tu tiempo en formular desde tu propio equipo varias soluciones de software adecuadas, deja que el arquitecto y tus mejores hombres realicen prototipos de la tecnología que usarían y selecciona la mejor opción. Deja que sus mentes vuelen, permítete ser creativo. Pero finalmente deja a tu arquitecto seleccionar la mejor solución posible. Y en esto no seas transparente con tu cliente.

Tercero: Cuando su cliente le comunique sus requerimientos, escúchelo pero con su solución ya en mano, arrójese a persuadirlo y guiarlo hacia el camino que ya de antemano decidió de manera que para el sea natural y casi de su propia invención. Pero no le cuentes tu plan en forma completa. Aquí puedes cometer fácilmente un error: NO le presente varias opciones. NO le presente una visión donde empiezas desarrollando un proyecto pero que requiere el desarrollo de varios módulos de un gran software. En lugar de eso, déjalo asumir que existen pocos requerimientos a la vez. Siempre intenta guiarlo y no dejes que se salga del sendero que tu estas trazando para el, sin embargo, siempre como primera medida dale la experiencia de sentir que esta descubriendo el camino por si solo.

“No hay nada mas apaciguador que ver que se hizo solamente lo que estaba planeado”

Cuarto: Siempre debes parecer dar mas de lo esperado, planea un feature o un aspecto del desarrollo con el que puedas sorprender a tu cliente el día de la entrega. Recuerda que la satisfacción de tu cliente no necesariamente esta dada por la satisfacción de los requerimientos, asume el hecho de que esa organización tiene naturaleza humana y en ese sentido dale experiencias enriquecedoras, dale la oportunidad de decir WOW en la reunión de entrega de iteración o release. No hay nada mas apaciguador que ver que se hizo solamente lo que estaba planeado, por eso contribuya a mantener una relación dinámica en el buen sentido de la palabra generando expectativas positivas e instruya a su equipo para que en todo contacto con su cliente se comporte como un profesional exitoso y dinámico.

expectationSi aplicas estos consejos verás como en cada fase del desarrollo tanto tu equipo trabajará en lo que cree es la mejor solución, como tu cliente se mantendrá convencido de que dicho es desarrollo es el mas pertinente posible. Sin embargo debo advertirte que algunos de estos consejos pueden ir en contravía de lo que tu metodología puede decirte, por ejemplo, algunas veces Agile puede decirte que seas transparente con tu stakeholder (cliente) pero si lo haces no podrás entregar nada fuera de lo planeado como indico en el punto 4. Como este pueden existir otras diferencias. Analízalas con calma y verifica que existan formas de armonizarlas.

Por qué C# puede ser tan rápido como C o C++?


Muchas veces encuentro algo de desinformación entre las personas dando por sentado que un programa en C o C++ es enormemente mas rápido que un programa compilado para la plataforma .NET, ¿por qué algo como esto no es del todo cierto?

Aquí nombrare algunos de los factores mas importantes:

El compilador y el ciclo desde la codificación hasta la ejecución

C# es un lenguaje de alto nivel estandarizado bajo la norma ECMA-334 el cual es compilado por varias herramientas, los compiladores mas populares son:

  • El compilador incluido en Mono Runtime de Novell
  • El compilador incluido con .NET Framework Runtime de Microsoft

Ambos de estos compiladores parsean el código fuente entrante en C# y generan archivos de código ejecutable intermedio siguiendo los lineamientos de CIL como se describen en el documento ECMA-335, CIL comprende un lenguaje ensamblador orientado a objetos para una maquina virtual abstraída del modelo de computo del hardware con el fin de servir de puente para la representación de los programas en una forma generalizada, como consecuencia el juego de instrucciones de CIL no coincide con el juego de instrucciones de nuestras x86 ni AMD64.

Cuando un programa es ejecutado sobre la plataforma .NET el sistema de tiempo de ejecución realiza un paso de compilación adicional que traduce el código en CIL al código especifico de la maquina anfitriona usando técnicas JIT, es entonces cuando el programa es ejecutado.

Esta ultima compilación es el paso mas importante para comprender si puede o no existir degradación en el desempeño cuando se usa la plataforma .NET por tres razones:

  • La compilación JIT (Just-In-Time) genera código final usando el juego de instrucciones nativo de la maquina anfitriona (puede generar para x86 y para AMD64) durante la marcha (con el fin de compensar latencia de arranque y consumo de memoria) incluyendo las optimizaciones a las que halla lugar, sin embargo este código compilado es cacheado y el proceso solo es necesario realizarlo una vez.
  • El desarrollador tiene la opción incluir la realización de este paso durante la instalación en la maquina anfitriona. (con NGEN.EXE si usa la implementación de Microsoft; con la opción AOT si usa la implementación de Novell)
  • El desarrollador tiene la opción de realizar este paso durante la construcción (Ahead of Time). Es esta característica la que ha hecho posible la ejecución de programas escritos para .NET en las plataformas iOS (iPad, iPod)

Ya que finalmente código fuente tanto en C, C++ y C# terminará siendo traducido a código en el conjunto de instrucciones nativo de la arquitectura del anfitrión es muy probable que su tiempo de ejecución sea similar.

La gestión de la memoria

Dado que la maquina para la cual se construyen los programas en CIL es una maquina abstraída del hardware real (Virtual Execution System abreviada VES), es importante hacer la salvedad de que su modelo de memoria conserva los elementos mas populares de nuestros modelos de computo real que son: considerar un espacio de almacenamiento de propósito general como un arreglo de bytes direccionable (homologo a la RAM), y el concepto de pila (homologo a la RAM usada como Pila). Estos elementos permiten que para el programador el concepto de la memoria se conserve pese a que el concepto del VES no es totalmente equivalente al de la maquina anfitriona.

En C# la memoria es gestionada automáticamente, liberando al programador de la tarea de destrucción de las instancias de objetos. Pese a la opinión casi generalizada de que la gestión automática de memoria es un hueco para el desempeño, la invariante tras los algoritmos de recolección de basura conservan la esencia de que un objeto debe ser destruido una vez es inalcanzable por el código fuente (lo que en otras palabras es cuando no lo usarás mas). En la implementación de .NET el algoritmo de recolección de basura (Garbage Collection) es un hibrido basado en el algoritmo generacional el cual realiza un full-copy eventual con el fin de mejorar la estabilidad del sistema al intentar eliminar la fragmentación en las concesiones de memoria, este hecho no es posible en los lenguajes como C o C++ ya que las asignaciones de memoria se mantienen fijas en los valores de los punteros devueltos por las funciones de alojamiento (malloc(), operador new()); pese a que un full-copy va en detrimento de la velocidad de ejecución, en la práctica la frecuencia con la que ocurre es tan baja que su impacto se mantiene imperceptible.

Reflexión, Dominio y Algoritmia – Cuando .NET es verdaderamente lento?

La utilización intensiva o incorrecta de algunas características incluidas en la plataforma .NET le impiden desempeñarse adecuadamente, por ejemplo la funcionalidad de reflexión (introspección en otros lenguajes) es conocida por atraer lentitud a los programas que abusan de ella, esto es debido a que esta característica usualmente debe a travesar bloques de código que deben ser bien chequeados para no permitir acceso a secciones de memoria no autorizadas (Cuando la aplicación viola las reglas del dominio de la aplicación – AppDomain, de demanda de permisos o de Zona de confianza). Sin embargo el problema del bajo rendimiento en la reflexión es ampliamente solventado usando bibliotecas como Cecil que le permiten la misma funcionalidad sin incurrir en el coste del chequeo de seguridad.

El amplio computo de propósito general esta lejos de los escenarios aquí nombrados por lo que finalmente se mantiene la premisa de que la complejidad temporal de un programa es en comparativamente mas dependiente de su algoritmia que de la tecnología con la que se implementa.

Conclusión

Un programa no es de facto mas rápido solo por estar escrito en un lenguaje como C o C++, antes de hacer una afirmación como esa puede ser necesario tener en cuenta detalles sobre el proceso y las tecnologías de construcción y el tipo de funcionalidad.

Mucho de lo aquí dicho vale también para otros lenguajes basados en maquina virtual como el caso de Java. Incluso algunos entornos de ejecución Java pueden acelerar la ejecución de código en el hardware enviando bytecode directamente al procesador usando una tecnología llamada Jazelle disponible en muchos procesadores ARM (especialmente en teléfonos celulares).

Mi “top useful EDA software list” de 2011


Altium Designer  – http://products.live.altium.com/

Pros:

Altium Designer esta lleno de características que no voy a detallar aquí, pero si nombraré mis dos favoritas.

  • UI Acelerada en Hardware: Aprovecha tu tarjeta de video para renderizar el contenido (si señores no es mentira). Layouts de PCB complejos y también diagramas de bloques están totalmente acelerados en hardware, esto le da una usabilidad y sensación de interfaz veloz especialmente durante los paneos y los zooms continuos (non ticking).
  • Flujo de diseño de nivel de sistema integrado: Te permite diseñar sistemas completos usando FPGAs o microprocesadores mientras mantienes la coherencia entre los detalles del Layout PCB, del Diagrama de bloques, código fuente de nivel de software y las especificaciones del sistema (Backannotate). De esto se deriva que te deja OPTIMIZAR LA DISTRIBUCION DE ASIGNACION DE PINES DE LOS CHIPS BASADOS EN LA CERCANIA FISICA EN LA PCB dejándote mas fácil el proceso de layout. Muy útil para proyectos de nivel de sistema.

Contras:

  • Elevado costo: una licencia empieza alrededor de USD $5.000

LTspice IV – http://www.linear.com/designtools/software/

Pros:

  • Ultraliviano simulador análogo compatible con la sintaxis de SPICE también con interfaz grafica.
  • Puedes añadirle modelos SPICE (disponibles en las paginas de los fabricantes de dispositivos) muy fácil.
  • Maniobrable y rápido. Muy útil para simular rápidamente nuevos proyectos o hacer pruebas de concepto.
  • Gratis.
  • Puedes arrancar a trabajar en segundos sin configurar rutas, ni proyectos, ni directorios.

Contras:

  • Limitada interfaz de usuario.
  • No realiza simulación mixta.

ModelSim – http://www.model.com/

Pros:

  • Excelente simulador HDL. Rápido y estable. Te permite ver el estado de todas las señales internas en un sistema durante la simulación.
  • Consigues gratis versiones Starter mediante los fabricantes de FPGAs con buenas prestaciones o Student.

Contras:

  • Sin quejas hasta ahora Sonrisa
  • SOLO la edición COMPLETA SOPORTA CODE COVERAGE

Implementación de la arquitectura Zetta – PASO 4


Síntesis del diseño

Vamos a tomar nuestros (diagramas de bloques y archivos en VHDL)  y a partir de ellos vamos obtener una descripción de todo nuestro sistema totalmente en VHDL, Como? en Altium Designer:

Usamos la opción Compilar proyecto

compilePrj

Si por alguna extraña razón Llorón obtienes un error de la forma “Port PortName not matched to Sheet-Entry at Location”, configura el proyecto para ignorar estos errores (suena a trampa, y lo es!) en el menú Project + Project Options, en la pestaña Error Reporting desactiva busca el ítem Port not linked to parent sheet-symbol y a su modo de reporte donde dice Fatal Error ajústalo a No Report. En mi caso: luego de compilar, guardar, cerrar, y volver a ajustar esta propiedad a la normalidad (Fatal Error), el problema dejo de presentarse en las compilaciones Sonrisa.

Fixbug

Añade un nuevo Constraint File haciendo click derecho en el proyecto.

AddConstraintFile

Yo añadí la línea al final del archivo para indicar la FPGA que voy a usar.

Record=Constraint | TargetKind=Part | TargetId=EP2C20F484C7

La cual también es posible usando un cuadro de dialogo mas amigable mediante el menú Design + Add/Modify Constraint + Part…

fpgaSel

Luego de compilar exitosamente usa el comando Síntesis haciendo click derecho en el proyecto.

Luego de esto puedes ver los archivos generados por el comando de sintesis como se muestra en esta figura.

VhdlGenerated

En estos archivos generados podemos ver como Altium ha hecho escrito todo el cableado por nosotros. Ahora cono el sistema totalmente en VHDL podemos utilizar cualquier herramienta de la industria para continuar nuestro trabajo (simuladores, sintetizadores para FPGA, compiladores de diseño, etc).

La síntesis también genera un archivo de extension .EDN usualmente llamado netlist EDIF, este archivo contiene nuestro diseño convertido a primitivas lógicas pero esa es otra historia.

Simulación

En este paso vamos a realizar una simulación de nuestro procesador usando el software MentorGraphics ModelSim.

Este software es de pago, pero podemos usar una edición gratuita (que puedes descargar aquí) para nuestros propósitos preparada por el fabricante Altera formalmente llamada ModelSim-Altera Starter Edition yo uso la mas reciente al momento de escribir esto (v6.6d for Quartus II v11.0SP1).

  • Debemos crear un nuevo proyecto en ModelSim usando File+New Project…
  • Ajusta el directorio a una carpeta nueva para no polucionar un solo folder.
  • Añade al proyecto todos los archivos VHD que están en la carpeta del proyecto de Altium, preferiblemente como una referencia y no copiando los archivos, Y RECUERDA AÑADIR TAMBIEN LOS ARCHIVOS VHD GENERADOS. Generalmente estan en la carpeta ProjectOutputs/
  • Usa Compile+Compile All.  Todos los archivos deben compilar sin problemas.
  • Usando ModelSim abre cualquier archivo VHD en el proyecto.
  • Con el foco en el editor de código, aparece un menú llamado Source. Úsalo y activa en el la opción Show Language Templates
  • En el panel que aparece llamado Language Templates, usa la opción Create Testbench.
  • En el cuadro de dialogo selecciona la unidad de diseño Entidad sys que esta dentro del Library work. presiona Next y Finish.

Verás que ModelSim ha creado un nuevo archivo llamado sys_tb.vhd y lo ha añadido al proyecto. En el no hay mas que una entidad sin entradas ni salidas que instancia a la entidad sys y le cablea todas su señales pero que no les asigna ningún valor.

Este archivo se llama unTESTBENCH, es un archivo en el que se instancian las unidades de diseño que vamos a someter a prueba y les cableamos y les asignamos señales que cambian en el tiempo para poder ver como se comportan. OJO: la clave entre un archivo de diseño para sintesis y uno para testbench es que en el ultimo normalmente no hay entradas ni salidas y las señales cambian en el tiempo. Por eso es común encontrar partículas wait o after en el código de un testbench.

Añade esto al cuerpo de la arquitectura en el nuevo sys_tb.vhd:

clk <= not clk after 50 ns; -- Generacion de CLK

resetgen: process  -- Generacion de RESET begin reset <= '1'; wait for 300 ns; reset <= '0'; wait; end process;

También inicializa la señal clk en 0 añadiendo := ‘0’ a la declaración de la señal, la línea debería quedarte así:

  SIGNAL clk   :  std_logic := '0' ; 

Compila este archivo del proyecto haciendo click derecho sobre el y escogiendo Compile+Compile Selected.

Aquí les muestro como va mi pantalla.

screenModelSim

Para simular escoge la pestaña Library (en la imagen esta donde queda) y selecciona dentro de la library work la entidad sys_tb, esta entidad corresponde al testbench (banco de prueba) que acabamos de crear. Dale click derecho y enseguida Simulate. La pantalla cambiará drásticamente para mostrarnos el entorno de simulación.

Configura la simulación como puedes ver en la imagen en el orden en que se muestra.

LaunchSimulation

Implementación de la arquitectura Zetta – PASO 3


Ahora que ya conocemos los detalles de que hace cada bloque en el diagrama de la arquitectura, procedemos a bajar un poco mas en el nivel de abstracción y entonces implementaremos cada uno de los bloques necesarios usando VHDL.

Bloque I_IRam – IRam.vhd

--------------------------------------------------------------------------------
-- SubModule IRam
-- Created   12/09/2011 11:03:47 a.m.
--------------------------------------------------------------------------------
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
use ieee.std_logic_unsigned.all;

entity IRam is port
   (
     address        : in    std_logic_vector(31 downto 0);
     data           : out   std_logic_vector(31 downto 0)
   );
end IRam;
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
architecture rtl of IRam is
    -- Tipos para la memoria ROM
    subtype word_t is std_logic_vector(31 downto 0);
    type memory_t is array(0 to 255) of word_t;

    -- funcion para inicializar la memoria
    function init_rom
        return memory_t is
        variable tmp : memory_t := (others => (others => '0'));
        variable s : std_logic_vector(7 downto 0);
    begin
    		-- Inicializa la memoria
        -- Por ahora solo vamos a inicializar contenido "basura", cuando
        -- estemos usando el procesador sera necesario cambiar esta
        -- inicializacion por un programa verdadero (secuencia de instrucciones
        -- de nuestro procesador Zetta)
        for addr_pos in 0 to 255 loop
            s := std_logic_vector(to_unsigned(addr_pos, s'length));
            tmp(addr_pos) := s & s & s & s; -- uso esto solo para probar
        end loop;
        return tmp;
    end init_rom;

    -- Declara la señal con el tipo de memoria y asigna una funcion
    -- de inicializacion, Quartus II creara un archivo (.mif)
    -- para esta memoria basado en esta inicializacion
    signal rom : memory_t := init_rom;

begin
	-- retorna la posicion de memoria
  data <= rom(conv_integer(address));
end rtl;
--------------------------------------------------------------------------------

Bloque I_UnpackInst – UnpackInst.vhd

--------------------------------------------------------------------------------
-- SubModule UnpackInst
-- Created   12/09/2011 11:04:05 a.m.
--------------------------------------------------------------------------------
library IEEE;
use IEEE.Std_Logic_1164.all;

entity UnpackInst is port
   (
     INST         : in    std_logic_vector(31 downto 0);
     OPCODE       : out   std_logic_vector(5 downto 0);
     RD           : out   std_logic_vector(4 downto 0);
     RS1          : out   std_logic_vector(4 downto 0);
     RS2          : out   std_logic_vector(4 downto 0);
     IMM          : out   std_logic_vector(15 downto 0);
     VAL          : out   std_logic_vector(25 downto 0)
   );
end UnpackInst;
--------------------------------------------------------------------------------

--------------------------------------------------------------------------------
architecture Structure of UnpackInst is
begin
     opcode <= inst(31 downto 26);
     rd <= inst(25 downto 21);
     rs1 <= inst(20 downto 16);
     rs2 <= inst(15 downto 11);
     imm <= inst(15 downto 0);
     val <= inst(25 downto 0);
end Structure;
--------------------------------------------------------------------------------

Bloque I_RegFile – RegFile.vhd

--------------------------------------------------------------------------------
-- SubModule RegFile
-- Created   12/09/2011 11:04:13 a.m.
--------------------------------------------------------------------------------
library IEEE;
use IEEE.Std_Logic_1164.all;
use IEEE.std_logic_unsigned.all;

entity RegFile is port
   (
     rd_data         : in    std_logic_vector(31 downto 0);
     rd_wrl           : in    std_logic;
     rd_wrh           : in    std_logic;
     rd              : in    std_logic_vector(4 downto 0);
     rs1             : in    std_logic_vector(4 downto 0);
     rs2             : in    std_logic_vector(4 downto 0);
     rs1_data        : out   std_logic_vector(31 downto 0);
     rs2_data        : out   std_logic_vector(31 downto 0);
     clk             : in    std_logic
   );
end RegFile;
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
architecture Structure of RegFile is
  type regs_t is array(0 to 31) of std_logic_vector(15 downto 0);
  signal regsh : regs_t;
  signal regsl : regs_t;
begin

     process(clk)
     begin
     if rising_edge(clk) then
        -- Engancha la parte baja
        if rd_wrl='1' then
          regsl(conv_integer(rd)) <= rd_data(15 downto 0);
        end if;
        -- Engancha la parte alta
        if rd_wrh='1' then
          regsh(conv_integer(rd)) <= rd_data(31 downto 16);
        end if;
     end if;
     end process;

    -- Muxes
    rs1_data <= regsh(conv_integer(rs1)) & regsl(conv_integer(rs1));
    rs2_data <= regsh(conv_integer(rs2)) & regsl(conv_integer(rs2));

end Structure;
--------------------------------------------------------------------------------

Bloque I_Alu – Alu.vhd

--------------------------------------------------------------------------------
-- SubModule Alu
-- Created   12/09/2011 11:04:20 a.m.
--------------------------------------------------------------------------------
library IEEE;
use IEEE.Std_Logic_1164.all;
use IEEE.std_logic_unsigned.all;
use ieee.std_logic_arith.all;

entity Alu is port
(
  A           : in    std_logic_vector(31 downto 0);
  B           : in    std_logic_vector(31 downto 0);
  R           : out   std_logic_vector(31 downto 0);
  FLAGS       : out   std_logic_vector(2 downto 0);
  OP          : in    std_logic_vector(3 downto 0)
);
end Alu;
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
architecture Structure of Alu is

  signal rres : std_logic_vector(31 downto 0);
begin

  process(a, b, op)
  begin
  case conv_integer(op) is
    when 0 => rres <= a + b;
    when 1 => rres <= a - b;
    when 2 => rres <= CONV_STD_LOGIC_VECTOR(conv_integer(a * b), 32);
    when 3 => rres <= a and b;
    when 4 => rres <= a or b;
    when 5 => rres <= not a;
    when 6 => rres <= shr(a, b);
    when 7 => rres <= shl(a, b);
    when others => rres <= a;
  end case;
  end process;

  r <= rres;
  flags(0) <= '1' when rres = 0 else '0';
  flags(1) <= '1' when rres > 0 else '0';
  flags(2) <= '1' when rres < 0 else '0';
end Structure;
--------------------------------------------------------------------------------

Bloque I_Mux – Mux.vhd

--------------------------------------------------------------------------------
-- SubModule Mux
-- Created   12/09/2011 11:04:29 a.m.
--------------------------------------------------------------------------------
library IEEE;
use IEEE.Std_Logic_1164.all;
use ieee.std_logic_unsigned.all;

entity Mux is port
   (
     a         : in    std_logic_vector(15 downto 0);
     b         : in    std_logic_vector(31 downto 0);
     o         : out   std_logic_vector(31 downto 0);
     sel       : in    std_logic_vector(1 downto 0);
     c         : in    std_logic_vector(31 downto 0)
   );
end Mux;
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
architecture Structure of Mux is
begin
  process(a,b,c, sel)
  begin
  case conv_integer(sel) is
  	when 0 => o <= a & a; -- "&" significa concatenar, NO es la operacion AND
    when 1 => o <= b; -- seleccion de b
    when others => o <= c; -- seleccion de c
  end case;
  end process;
end Structure;
--------------------------------------------------------------------------------

Bloque I_PC – PC.vhd

--------------------------------------------------------------------------------
-- SubModule PC
-- Created   12/09/2011 11:10:36 a.m.
--------------------------------------------------------------------------------
library IEEE;
use IEEE.Std_Logic_1164.all;
use ieee.std_logic_unsigned.all;

entity PC is port
   (
     pc         : out   std_logic_vector(31 downto 0);
     inc        : in    std_logic_vector(25 downto 0);
     jump       : in    std_logic_vector(1 downto 0);
     clk        : in    std_logic;
     reg        : in    std_logic_vector(31 downto 0);
     reset      : in    std_logic
   );
end PC;
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
architecture Structure of PC is
   signal rpc : std_logic_vector(31 downto 0);
begin
     process(clk, reset)
     begin
     if reset = '1' then
     -- Usamos reset asincrono para volver a cero
        rpc <= (others => '0');
     elsif rising_edge(clk) then
     -- En el flanco de subida realizamos el salto si es requerido o
     -- incrementamos en 1 si no es necesario el salto
        case(conv_integer(jump)) is
        when 0 => rpc <= rpc + 1; -- incremento
        when 1 => rpc <= rpc + inc; -- salto relativo
        when others => rpc <= reg; -- salto absoluto
        end case;
     end if;
     end process;

     pc <= rpc;

end Structure;
--------------------------------------------------------------------------------

Bloque I_Control – Control.vhd

--------------------------------------------------------------------------------
-- SubModule Control
-- Created   12/09/2011 11:03:37 a.m.
--------------------------------------------------------------------------------
Library IEEE;
Use IEEE.Std_Logic_1164.all;
use ieee.std_logic_unsigned.all;
use ieee.std_logic_arith.all;

entity Control is
  port(
    opcode      : in  std_logic_vector(5 downto 0);
    flags       : in  std_logic_vector(2 downto 0);
    jump        : out std_logic_vector(1 downto 0);
    sel_rd_data : out std_logic_vector(1 downto 0);
    alu_op      : out std_logic_vector(3 downto 0);
    mem_rd      : out std_logic;
    mem_wr      : out std_logic;
    rd_wrl      : out std_logic;
    rd_wrh      : out std_logic
  );
end Control;
--------------------------------------------------------------------------------
architecture Structure of Control is
  type topcode is (
    OP_ADD, OP_SUB, OP_MUL, OP_AND, OP_OR, OP_NOT, OP_SL, OP_SR, -- Regs to Reg
    OP_MOV, -- Reg to Reg
    OP_JR, OP_J, OP_JE, OP_JNE, OP_JL, OP_JG, -- Jumps
    OP_LH, OP_LL, -- Load immediate
    OP_LW, OP_SW -- Load/Store Memory (Read/Write External Bus)
    );
  -- Tipo de salto
  type tjump is (J_NONE, J_JUMP_INC, J_JUMP_REG);
  -- Tipo de operacion posible en la ALU
  type taluop is (AO_ADD, AO_SUB, AO_MUL, AO_AND, AO_OR, AO_NOT, AO_SR, AO_SL, AO_BYPASS);
  -- Selectores de informacion que va al registro de destino
  type tselrd is (SR_IMM, SR_ALU, SR_MEM);
  -- Tipo de operacion de memoria / bus
  type tmemop is (MO_NONE, MO_READ, MO_WRITE);
  -- Tipos de escritura al register file
  type trfw is (RF_NONE, RF_BOTH, RF_LOW, RF_HIGH);

  signal iopcode   : topcode;
  signal ijump     : tjump;
  signal ialuop    : taluop;
  signal iselrd    : tselrd;
  signal imemop    : tmemop;
  signal irfw      : trfw;
  signal zeroflag  : std_logic;
  signal greatflag : std_logic;
  signal lessflag  : std_logic;
begin

  -- convierte el valor de entrada en valor simbolico
  iopcode   <= topcode'val(conv_integer(opcode));
  zeroflag  <= flags(0);
  greatflag <= flags(1);
  lessflag  <= flags(2);

  -- convierte los valores simbolicos en valores de salida
  jump        <= conv_std_logic_vector(tjump'pos(ijump), jump'length);
  alu_op      <= conv_std_logic_vector(taluop'pos(ialuop), alu_op'length);
  sel_rd_data <= conv_std_logic_vector(tselrd'pos(iselrd), sel_rd_data'length);
  mem_rd      <= '1' when imemop = MO_READ else '0';
  mem_wr <= '1' when imemop = MO_WRITE else '0';
  rd_wrl <= '1' when irfw = RF_BOTH or irfw = RF_LOW else '0';
  rd_wrh <= '1' when irfw = RF_BOTH or irfw = RF_HIGH else '0';

  process(iopcode, zeroflag, greatflag, lessflag)
  begin
    case iopcode is
      when OP_ADD =>
        ijump  <= J_NONE;
        iselrd <= SR_ALU;
        imemop <= MO_NONE;
        irfw   <= RF_BOTH;
        ialuop <= AO_ADD;
      when OP_SUB =>
        ijump  <= J_NONE;
        iselrd <= SR_ALU;
        imemop <= MO_NONE;
        irfw   <= RF_BOTH;
        ialuop <= AO_SUB;
      when OP_MUL =>
        ijump  <= J_NONE;
        iselrd <= SR_ALU;
        imemop <= MO_NONE;
        irfw   <= RF_BOTH;
        ialuop <= AO_MUL;
      when OP_AND =>
        ijump  <= J_NONE;
        iselrd <= SR_ALU;
        imemop <= MO_NONE;
        irfw   <= RF_BOTH;
        ialuop <= AO_AND;
      when OP_OR =>
        ijump  <= J_NONE;
        iselrd <= SR_ALU;
        imemop <= MO_NONE;
        irfw   <= RF_BOTH;
        ialuop <= AO_OR;
      when OP_NOT =>
        ijump  <= J_NONE;
        iselrd <= SR_ALU;
        imemop <= MO_NONE;
        irfw   <= RF_BOTH;
        ialuop <= AO_NOT;
      when OP_SL =>
        ijump  <= J_NONE;
        iselrd <= SR_ALU;
        imemop <= MO_NONE;
        irfw   <= RF_BOTH;
        ialuop <= AO_SL;
      when OP_SR =>
        ijump  <= J_NONE;
        iselrd <= SR_ALU;
        imemop <= MO_NONE;
        irfw   <= RF_BOTH;
        ialuop <= AO_SR;
      when OP_MOV =>
        ijump  <= J_NONE;
        iselrd <= SR_ALU;
        imemop <= MO_NONE;
        irfw   <= RF_BOTH;
        ialuop <= AO_BYPASS;

-- DC: significa Dont-Care (no importa)
      when OP_JR =>
        ijump  <= J_JUMP_REG;
        iselrd <= SR_ALU;       -- DC
        imemop <= MO_NONE;
        irfw   <= RF_NONE;
        ialuop <= AO_BYPASS;    -- DC

      when OP_J =>
        ijump  <= J_JUMP_INC;
        iselrd <= SR_ALU;       -- DC
        imemop <= MO_NONE;
        irfw   <= RF_NONE;
        ialuop <= AO_BYPASS;    -- DC

      when OP_JE =>
        if zeroflag = '1' then
          ijump <= J_JUMP_INC;
        else
          ijump <= J_NONE;
        end if;
        iselrd <= SR_ALU;       -- DC
        imemop <= MO_NONE;
        irfw   <= RF_NONE;
        ialuop <= AO_BYPASS;    -- DC
      when OP_JNE =>
        if zeroflag = '0' then
          ijump <= J_JUMP_INC;
        else
          ijump <= J_NONE;
        end if;
        iselrd <= SR_ALU;       -- DC
        imemop <= MO_NONE;
        irfw   <= RF_NONE;
        ialuop <= AO_BYPASS;    -- DC
      when OP_JL =>
        if lessflag = '1' then
          ijump <= J_JUMP_INC;
        else
          ijump <= J_NONE;
        end if;
        iselrd <= SR_ALU;       -- DC
        imemop <= MO_NONE;
        irfw   <= RF_NONE;
        ialuop <= AO_BYPASS;    -- DC
      when OP_JG =>
        if greatflag = '1' then
          ijump <= J_JUMP_INC;
        else
          ijump <= J_NONE;
        end if;
        iselrd <= SR_ALU;       -- DC
        imemop <= MO_NONE;
        irfw   <= RF_NONE;
        ialuop <= AO_BYPASS;    -- DC

      when OP_LH =>
        ijump  <= J_NONE;
        iselrd <= SR_IMM;
        imemop <= MO_NONE;
        irfw   <= RF_HIGH;
        ialuop <= AO_BYPASS;    -- DC
      when OP_LL =>
        ijump  <= J_NONE;
        iselrd <= SR_IMM;
        imemop <= MO_NONE;
        irfw   <= RF_LOW;
        ialuop <= AO_BYPASS;    -- DC

      when OP_LW =>
        ijump  <= J_NONE;
        iselrd <= SR_MEM;
        imemop <= MO_READ;
        irfw   <= RF_BOTH;
        ialuop <= AO_BYPASS;    -- DC
      when OP_SW =>
        ijump  <= J_NONE;
        iselrd <= SR_MEM;       -- DC
        imemop <= MO_WRITE;
        irfw   <= RF_NONE;
        ialuop <= AO_BYPASS;    -- DC

      when others =>
        ijump  <= J_NONE;
        iselrd <= SR_ALU;
        imemop <= MO_NONE;
        irfw   <= RF_NONE;
        ialuop <= AO_NOT;
    end case;
  end process;
end Structure;
--------------------------------------------------------------------------------

Descripción terminada…

La descripción del sistema en su forma funcional esta terminada. Con esto me refiero a que en este momento tenemos todo un diseño que incluye elementos básicos descritos con VHDL y cableados mediante diagramas de bloques (de manera que es mas sencilla su interpretación) para así formar un sistema completo. Recordemos los elementos que eran necesarios para describir un sistema en forma completa:

El paso siguiente  será obtener a partir de todo esto, una descripción totalmente en HDL (sin diagramas de bloques) de manera que nuestro sistema pueda ser simulado. Para lograr esto, le pediremos a Altium que convierta nuestros diagramas de bloques en archivos VHDL que representan el mismo cableado.

Noten que pudimos haber escrito todo el sistema usando VHDL, pero visualizar la estructura a partir del código VHDL no es para nada intuitivo, por el contrario la claridad de nuestro objetivo se pierde en el código, Es mucho mas intuitivo ingresar el diagrama al software y mantener estos diagramas por ello usamos una herramienta que nos facilitara el trabajo.

Implementación de la arquitectura Zetta – PASO 2


Ahora que tenemos un horizonte claro del sistema que estamos construyendo demos un paso hacia ABAJO (ya que estamos usando la metodología Top-Down).

Al bajar lo que estamos haciendo es adentrándonos en los detalles del sistema para llevar a cabo su implementación, esto es lo mismo que aumentar el nivel de detalle.

Usando en Altium podemos “bajar” usando la herramienta Create Sheet From Sheet Symbol. como se muestra en la imagen. Esto nos creará un nuevo diagrama de bloques con los puertos definidos en el bloque de manera que podemos ahi diseñar el interior de la caja negra (para que deje de serlo).

zetta1a

A continuación una imagen que refleja mi diseño en este nivel.

zetta2

En esta imagen se puede ver como he compuesto un sistema a partir de nuevos bloques funcionales que todavía no he definido internamente (por ahora son cajas negras)

Para que quede mas claro lo que hemos hecho aquí muestro como se relacionan los bloques.

zetta2a

Este nivel muestra claramente elementos nuevos que son producto de la creatividad del diseñador / ingeniero en donde se divide el problema general (procesador ZETTA) en subproblemas mas pequeños (Register File, ALU, Memoria de programa, Unidad de Control, Program counter). OJO: Note que las señales en magenta son señales que salen de la unidad de control, y están conectadas entre si cuando llevan el mismo nombre. Estas líneas no se extienden por todo el diagrama SOLO PARA PODER CONSERVAR EL DIAGRAMA ORDENADO Y UN POCO MAS CLARO DE ENTENDER.

Explicación del diseño

Bloque I_IRam = Memoria de Instrucciones

Esta es una memoria que contiene el programa que ejecutará el procesador que estamos construyendo. Aquí residen las instrucciones una a una en cada posición que esta almacena. Esta memoria saca a su salida data la instrucción en la posición que indica la señal address (que viene del contador de programa).

Bloque I_UnpackInst = Instruction Decoder / Desempacador de Instrucción

Este bloque toma la instrucción que sale de la memoria de instrucciones (I_IRam)  y separa los elementos que la constituyen para poder trabajarlos en los otros bloques. Los elementos que constituyen una instrucción estaban definidos cuando definimos la arquitectura en otro articulo.

Bloque I_PC = Program Counter / Contador de Programa

Registro que indica la dirección de la instrucción que esta siendo ejecutada.

Bloque I_RegFile = Register File / Banco de Registros

Es un conjunto de registros que el programador puede utilizar para realizar operaciones con los datos que desee procesar. Dependiendo de la instrucción que se ejecute se operan ciertos registros, por eso las entradas incluyen dos selectores de registro rs1 y rs2; y las salidas rs1_data y rs2_data son respectivamente la información de estos registros seleccionados.

Como también es posible guardar resultados de algunas instrucciones en un registro, este bloque también incluye unas entrada rd_wrh y rd_wrl que cuando están en 1 indican que se debe guardar la parte alta o baja respectivamente; una entrada rd que selecciona el registro de destino; y una entrada rd_data con los datos que serán guardados.

Bloque I_Alu = Arithmetic & Logic Unit / Unidad Aritmética y Lógica

Es un bloque que realiza las operaciones aritméticas o lógicas necesarias para poder cumplir con la ejecución de algunas instrucciones. Recibe la información de los registros seleccionados en el banco de registros y los opera (los suma, o los resta, o los multiplica, según sea el caso) dejando ver a su salida el resultado de la operación.

Bloque I_Mux = Multiplexor / Multiplexor

Este multiplexor permite seleccionar de donde se van a sacar los datos que se guardarán en el registro de destino de I_RegFile.

En el registro de destino se puede guardar un dato que es….

  • Proveniente del Bus o puerto de comunicación externo.
  • Resultado de una operación entre registros mediante la ALU
  • Un valor inmediato codificado en instrucciones LL y LH (instrucciones I-Type)

Bloque I_Control = Control Unit / Unidad de Control

La unidad de control aplica la lógica necesaria para configurar cada uno de los otros bloques según corresponda el caso para cumplir con la instrucción que esta siendo ejecutada. Es por eso que su principal entrada es opcode.