Herramientas Personales

Clase del martes 25 de febrero de 2014

por Martín Guillén, Sergio Última modificación 05/03/2014 18:04

(Los deberes para la siguiente clase son "intentar hacer más ejercicios del boletín" pues ya hemos visto toda la teoría. Consultar dudas al profesor ya sea por correo o en tutorías. Además, como el próximo día empezaremos tema nuevo, el 2, ir mirando el PDF de dicho tema.)

 

 

En esta quinta estuvimos resolviendo problemas del boletín de Verilog, relacionados con describir en Verilog diversos circuitos o módulos de "testbench".

  • El primer problema que realizamos (a petición de algunos alumnos) fue el 6, que consiste en describir en Verilog es un circuito combinacional relativamente sencillo.
  1. Quizá la dificultad estriba en que no tenemos claro lo que hace. Se trata simplemente de un circuito que tiene 7 salidas, encargadas de activar (o no activar) los segmentos luminosos de un display de 7 segmentos. Partiendo de que conocemos (o nos inventamos) una asignación entre salidas y segmentos, es fácil hacer la tabla de verdad del circuito.
  2. La tabla de verdad la planteamos pensando que la entrada del circuito eran números del 0 al 9 (en decimal), por lo que el circuito tiene solo 4 bits de entrada. Quiere esto decir que para las entradas con valores entre 10 y 15 nos dará igual cual sea la salida del circuito. Esto es interesante porque sabemos que las inespecificaciones en la salida del circuito darán lugar, como sabemos, a circuitos más simples.
  3. Planteamos la descripción del módulo de forma procedimental, con un "case". Estudiamos lo que ocurriría si en el "case" solo consideramos los 10 valores del 0 al 9 y no consideremos los demás: Resultaría un circuito que funciona bien, pero no es "combinacional", sino "secuencial". Esto no es lo que queremos que ocurra cuando "sinteticemos" el circuito con la herramienta adecuada (el entorno ISE, por ejemplo).
  4. Explicamos que lo correcto es que "case" se encargue de asignar siempre un valor a las salidas del circuito, sea cual sea el valor de las entradas (aunque sepamos que en la práctica esos valores no se van a dar), para que el circuito equivalente que se "sintetice" sea combinacional.
  5. La forma de conseguir esto fue añadiendo como último elemento del "case" una "default" que tuviese encuenta lo que hacer cuando las entradas no sean un número del 0 al 9. Concretamente lo que hicimos fue asignar a las salidas el valor 'bX que no es más que decir que las salidas no están especificadas en ese caso. Es decir, que se deja libertad a la herramienta de síntesis a que diseñe un circuito que para esos valores de entrada de el valor que quiera, con objeto de que (a lo mejor) así obtenga un circuito más simple. Forzar un valor específico para las salidas es otra posibilidad. Cualquier valor sería adecuado, pero lo normal es que en ese caso el circuito que se genere sea algo más complejo que usando inespecificaciones.
  6. Este ejercicio nos sirvió de excusa para ver distintas formas de expresar las constantes, en decimal, en binario, etc. así como para ilustrar lo cómodo que resulta el uso del operador de concatenaciçon ( "{" y "}" ) a la hora de asignar valores "de golpe" a un conjunto de datos de tamaño bit.
  • El segundo problema que realizamos fue el 5, un codificador 4 a 2, que no es otra cosa que un circuito que hace justo la tarea inversa a un decodificador. El codificador nos indica con una salida de dos bits el número de la entrada que está activada. Si ninguna de las 4 entradas está activada, hay una tercera salida que nos avisa de esta circunstancia. Una cuestión importante en los codificadores es qué hacer si se activa más de una entrada a la vez. Se podría actuar como si no se hubiese activado ninguna entrada, o bien avisar con una nueva salida del hecho "anómalo", aunque lo que hace el circuito que resolvimos es considerar que unas entradas son más prioritarias que otras y mostrar en la salida el número de la salida más prioritaria.
  1. La solución que propusimos era procedimental. Se podría haber usado un "case", pero tendríamos que haber considerado los 16 valores posibles de la entrada al circuito.
  2. Dado que hay entradas más prioritarias que otras, preferimos plantear una estructura if-else, en la cual primero preguntábamos si estaba activa la entrada más prioritaria. Si era cierto entonces poníamos su número en la salida. Si no, entonces comprobábamos la siguiente entrada más prioritaria, para poner su número en la salida si es que estaba activada. Nuevamente, en caso contrario, pasabamos a las siguientes entradas.
  3. Obviemante, tras comprobar por orden las cuatro entradas, si ninguna de ellas estaba activada entonces lo que hacemos es avisar de ello con la señal de salida prevista para esta circunstancia.
  4. Con este ejercicio hemos visto que la estructura if-else se muestra muy cómoda para describir tablas de verdad que están escritas de forma muy compacta porque ciertas entradas son más prioritarias que otras.
  • Pasamos ahora a realizar circuitos secuenciales. El primero que hicimos fue el del problema 13, un registro de desplazamiento que se nos decxribe mediante una tabla de funcionamiento.
  1. Es importante ser capaces de "dibujar" la estructura de un circuito a partir del enunciado del problema, así que eso fue lo primero que hicimos. Colocamos en el dibujo todas las entradas de control (CL, SHR), la entrada de reloj (CK) que es evidente que existe aunque no se menciona expresamente y también la entrada XR, que es el birt que "entra" en el registro cuando se da la orden de desplazar. El registro suponemos que es capaz de mostrar su contenido a traves de una salida W, de varios bits, que ya que no nos dicen cuantos son, podemos suponer 8.
  2. Explicamos el significado de la expresión SHR(REG,XR) comunmente utilizada para representar el desplazamiento a la derecha de un valor de 8 bits y la "entrada" de un bit por la izquierda, perdiéndose un bit de los ocho originales por la derecha. Este tipo de operaciones de manipulación de bits se suelen expresar en Verilog con mucha facilidad gracias al operador de concatenación "{" y "}". Concretamente se expresaría como {XR,REGISTRO[7:1]} la operación de desplazar los bits un REGISTRO de 8 bits una posición a la derecha metiendo un nuevo bit por la izquierda.
  3. Por norma general, al tratarse de un circuito secuencial tenemos obligatoriamente que emplear un dato de tipo "reg" para modelar el elemento capaz de guardar el "estado" a pesar de que ciertas entradas del circuito cambien de valor. Este dato de tipo "reg" podemos usarlo, en ocasiones, una de las salidas del circuito aunque en general es más recomendable (sobre todo si estamos dando nuestros primeros pasos en Verilog) dejar que este "reg" sea un dato interno y que sea la salida un dato de tipo "wire" distinto del anterior. Si luego es necesario que el "wire" de salida tenga un valor idéntico al "reg" que guarda el estado, bastará con un "assign" que se encargue de ello. Por ejemplo, podemos declarar con reg [7:0]REGISTRO; el dato que guarda el estado. En ese caso, con un assign W = REGISTRO; conseguimos que la salida W muestre en todo momento el valor del estado del circuito.
  4. Estuvimos explicando que en la lista de sensibilidad del bloque "always" con el que se modelará la forma en que cambia el "reg" que guarda el estado del circuito solo debe aparecer  la señal de relog, CK, precedidad de "posedge" o "negedge" y, solo en el caso de que haya señales de contro ASÍNCRONAS, éstas también aparecerán en dicaha lista con su correspondiente "posedge" o "negedge". No se puede incluir ninguna señal de contro SÍNCRONA en la lista y tampoco omitir el "posedge" o el "negedge".
  5. Como no nos dicen si el reloj es activo en flanco de subida, supusimos activo en flanco de subida y por tanto usamos "posedge" al incluirla en la lista de sensibilidad.
  6. La señal de borrado CL, es una señal de control ASÍNCRONA que podemos ver en la tabla de funcionamiento del enunciaco que es una señal activa en nivel bajo, así que hay que incluirla en la lista de sensibilidad y además precederla de la plalabra clave "negedge".
  7. La señal SHR es SÍNCRONA por lo que no la añadimos en la lista de sensibilidad.
  8. El interior del bloque "always" es bastante sencillo y, en general, para cualquier tipo de registro siempre lo primero que hay que comprobar es el motivo por el cual se ha "entrado" en el bloque "always".
  9. Como el enunciado nos dice que las operaciones asíncronas son prioritarias sobre las síncronas, lo que haremos será preguntar primero por las señales asíncronas (solo CL en este caso) y hacer con el "reg" que almacena el estado la operación correspondiente.
  10. Si no está activada ninguna señal síncrona, entonces es que hemos "entrado" en el bloque "always" porque se ha producido un flanco activo de reloj. Tenemos que comprobar si hay alguna señal de control síncrona y en caso afirmativo habrá que hacer con el "reg" que almacena el estado la operación correspondiente. Si no hay señal activa se entiede que el estado del circuito no cambia y no es necesario expresar esta circunstancia de ninguna manera.
  11. Aunque en este ejercicio solo hay una señal de control síncrona y otra asíncrona, si hubiera más de una de un tipo habría que ver si una es más prioritaria que la otra y comprobarla por tanto en primer lugar.
  12. Para completar el ejercicio planteamos una variante (no prevista en el enunciado original) en la cual el registro tiene una entrada de control OE ("output enable") de forma que si OE es 0, la salida del registro debe estar en alta impedancia y si OE es 1, la salida W muestra el valor del estado del circuito.
  13. En general, todas aquellas salidas que puedan obtenerse de forma "combinacional" a partir del estado del circuito (y quizás de otras señales) debemos describirlas fuera del bloque "always" en el que se modifica el estado del circuito. Así, en este caso concreto de la salida W que o bien es alta impedancia (HI) o bien es el valor del estado del circuito, nos encontramos conque podemos con un assign W = (OE==1) ? REGISTRO : 'bZ; expresar que el valor de W depende de forma "combinacional" del valor del estado del circuito y del valor de la entrada OE.
  14. Es la primera vez que usamos en clase el operador ternario ? : así que lo explicamos debidamente. Aparte del hecho de ser ternario (tiene tres operandos y no dos como los binarios o uno opcmo los unarios) no deja de ser un ooperador, por lo que "opera" con sus operandos y da un resultado. El resultado de la operación A ? B : C será B si A es "cierto" o B si A es "falso". Es una forma de conseguir, mediante una expresión que podemos ubicar dentro de una sentencia de asignación, lo que hubiera necesitado de un bloque procedimental y una sentencia "if".
  15. También es la primera vez que vemos la expresión 'bZ por lo que hay que explicarla también. Es la forma que tenemos de expresar en Verilog que un bit no vale 0 ni 1, sino que está a valor HI ("High Impedance" o "alta Impedancia"). Es lo que se consigue en un cable en el que no hay conectado nada y por tanto no hay nada que esté poniendo el cable a "nivel alto" (un 1) ni nada que lo esté poniendo a "nivel bajo" (un 0). Si asignamos a un dato de varios bits el valor 'bZ estamos indicando que todos los bits están en estado de "alta impedancia" y no a valor 0 ni valor 1. También se conoce el estado de "alta impedancia" como "estado flotante".
  • Un problema muy parecido al 13 es el problema 14, que es el siguiente que resolvimos.
  1. Se trata de un ejercicio algo más complejo porque se pueden hacer más operaciones sobre el registro, pero la idea base es la misma y podemos aprovechar la solución del ejercicio 13 como "esqueleto" sobre el cual ir construyendo la solución de este.
  2. La operación SHL(REG, XL) se expresaría en Verilog como {REGISTRO[6:0],XL} de forma que se pierde por la izquierda unos de los bits del registro, los otros siete se desplazan a la izquierda y entra uno nuevo por a derecha. La señal SHL gobierna esta operación.
  3. La operación de carga o "LOAD", se controla por una nueva señal, la LD, y esto nos oblia a tener una entrada de 8 bits que llamaremos X que es la que permite que entren "en paralelo" los 8 bits hacia el interior del registro.
  4. Por complicar un poco más el ejercicio planteamos la existencia de una señal DIEZ, de salida, de un solo bit, que se activase a valor 1 cuando el valor del estado del registro fuese 10 (en decimal) o 1010 (en binario). Esto se consigue de forma simple haciendo un assign DIEZ = (REGISTRO==4'b1010)?1:0; Nótese que hemos hecho como en el ejercicio anterior y hemos sacado fuera del bloque "always" que sirve para modificar el valor del estado registro el cálculo de las señales de salida que dependen combinacionalmente del valor del estado del registro.
  • Por último para ilustrar un tema un poco diferente pero igualmente importante, abordamos el problema 24, pero no la parte de la descripción Verilog del circuito, sino la parte del desarrollo del "testbench".
  1. El PDF del tema 1 describe con gran detalle el desarrollo de los "testbench". Recordé que es imprescindible estudiarse esta parte y me limité a esbozar a grandes rasgos cómo podría hacerse un módulo de "testbench" que se ajustase a lo que nos piden en el enunciado.
  2. Creamos la instancia del módulo a probar, conectándola a los "wire" y "reg" que hemos definido previamente en el "testbench".
  3. Generamos una señal de reloj, con un bloque always, algo imprescindible en un "testbench" de un módulo secuencial.
  4. Generamos la señal X para que evolucione en el tiempo según muestra el enunciado: Dos ciclos a valor 0, un ciclo a valor 1, un ciclo a valor 0, dos ciclos a valor 1, etc. Para ello es muy conveniente hacer uso de las sentencias de "espera" del tipo @(posedge CLK); para conseguir que la señal X tome los valores adecuados durante los tiempos necesarios.
  5. Hay que tener también presente que la sentencia repeat(N) nos ayuda a repetir N veces otras sentencias sin necesidad de escribirlas N veces.
Acciones de Documento