Mostrando entradas con la etiqueta VHDL. Mostrar todas las entradas
Mostrando entradas con la etiqueta VHDL. Mostrar todas las entradas
martes, 26 de junio de 2018
Hoja de apuntes de VHDL (cheatsheet))
Todas las hojas de apuntes de VHDL que he visto son estéticamente feas e incompletas, así que tuve que hacer la mía en Latex porque la necesitaré para la maestría. Utilice una plantilla bastante bonita que creo un alemán y que posteó en una respuesta en Stackoverflow.
El pdf incluye dos hojas que si se imprimen en dos caras puede doblarse como un folleto y tenerse siempre en la mochila.
viernes, 1 de diciembre de 2017
Multiplicación en punto fijo Pt. 2 [formato fixdt(signed,n,s,b)]
Esta entrada es una continuación del tema de multiplicación punto fijo y tocará el tema de un formato de mapeo lineal que permite ajustar números enteros a un rango lineal arbitrario como puede ser los rangos de voltaje estándar para DAC's y ADC's. El formato es el siguiente:
El rango de este formato queda definido por sus parámetros de la siguiente manera:
La multiplicación queda definida cómo:
La multiplicación es mas complicada que en el formato fixed(signed,n,l) pero se simplifica cuando b = 0 siendo en este caso similar a la multiplicación en el otro formato. El siguiente paso en definir el procedimiento para computar los valores de n, s y b según el intervalo continuo en que el que deseemos trabajar. Al igual que el formato anterior ,el parámetro n define los valores máximos para los enteres almacenados. Por tanto, teniendo definidos los límites del intervalo real y del intervalo entero, los valores de s y b se calculan resolviendo el siguiente sistema de ecuaciones:
Realizaremos un ejemplo práctico para demostrar la utilizad de este formato su ventaja con respecto a fixed(signed,n,l). Implementaremos en un FPGA un generador de señal cosenoidal (he escrito una entrada sobre los detalles de estos generadores). Utilizaremos una DAC LTC1661 que tiene una resolución de 10 bits y un rango de salida de 0-Vreff*(1023/1024). Las salidas Vcc de la tarjeta que voy a utilizar son de 3.3 volts por lo que este intervalo será aproximadamente de 0-3.3 V. Ya que esta DAC no puede dar salidas de voltaje negativo el valor para signed sera '0'. Con esta información podemos encontrar los valores para establecer nuestro formato. Podemos obtenerlos mediante este código de Matlab:
minimo = 0; %Volts
maximo = 3.3; % Volts
is_signed = false;
word_length = 10;
[E_min, E_max] = range(fi([], is_signed, word_length, 0));
A = double ([E_min, 1; E_max, 1]);
b = double ([minimo; maximo]);
x = A\b;
format long g
slope = x(1)
bias = x(2)
La ejecución del código nos da una pendiente s = 0.0032258064516129 y un bias b = 0. La señal para este ejemplo tendrá una frecuencia de 0.05 Hz y un periodo de muestreo de 1 segundo (si pueden usar un osciloscopio pueden probar con una frecuencia más alta y un periodo de muestro más corto). Generaremos un código VHDL con HDL Coder a partir de este modelo de Simulink:
Los subsitemas que he llamado Fixed-Point Multiplication contienene la implementación de la multiplicación que hemos descrito aquí. Dado que b = 0 la multiplicación de punto fijo simplemente se define como el producto de los enteros almacenados y la pendiente s:
Algo que podría resultar confuso y difícil de entender al principio es el por qué el producto con s se realiza en formato fixed(signed,n,l). La respuesta es que ese formato se utiliza para principalmente para la multiplicación de números fraccionales en un entorno que solo permita trabajar con números enteros de forma interna (que es el caso de nuestro sistema de hardware). El formato de mapeo lineal fixdt(signed,n,s,b) sólo se encarga de que las operaciones entre enteros tengan el sentido físico que esperamos de forma externa. Esto tambien explica un segundo hecho que tambien puede resultar confuso: las operaciones entre enteros se realizan en modo signed (para facilitar las restas) aunque se ha definido el modo unsigned.
Para generar el código nos vamos a Code > HDL Code > Generate HDL. Para la comunicación SPI con la LTC1661 he modificado un código que había publicado anteriormente. Los módulos son los siguiente:
Nota: durante la generación del código se indica que el reloj para el modulo del generador sea el doble del establecido. En este caso el divisor tiene una salida de 2 Hz.
Según la simulación, y si implementamos correctamente las cosas, las secuencia de voltaje que deberíamos ver experimentalmente a la salida del DAC es la siguiente:
Aquí muestro un video donde se puede ver que no tengo un buen multímetro y tuve que aumentar a 4 segundos el periodo de muestreo para dar tiempo de estabilizar las lecturas:
Referencias
Compute Slope and Bias, MathWorks
Fixed-Point: Rage and Precision, MathWorks
fixdt(signed,n,s,b)
- signed: toma el valor de 1 si el entero almacenado es signado y 0 en caso contrario.
- n: longitud en bits del entero almacenado.
- s: pendiente de la función de mapeo lineal.
- b: bias de la función de mapeo lineal.
El rango de este formato queda definido por sus parámetros de la siguiente manera:
La multiplicación queda definida cómo:
La multiplicación es mas complicada que en el formato fixed(signed,n,l) pero se simplifica cuando b = 0 siendo en este caso similar a la multiplicación en el otro formato. El siguiente paso en definir el procedimiento para computar los valores de n, s y b según el intervalo continuo en que el que deseemos trabajar. Al igual que el formato anterior ,el parámetro n define los valores máximos para los enteres almacenados. Por tanto, teniendo definidos los límites del intervalo real y del intervalo entero, los valores de s y b se calculan resolviendo el siguiente sistema de ecuaciones:
Realizaremos un ejemplo práctico para demostrar la utilizad de este formato su ventaja con respecto a fixed(signed,n,l). Implementaremos en un FPGA un generador de señal cosenoidal (he escrito una entrada sobre los detalles de estos generadores). Utilizaremos una DAC LTC1661 que tiene una resolución de 10 bits y un rango de salida de 0-Vreff*(1023/1024). Las salidas Vcc de la tarjeta que voy a utilizar son de 3.3 volts por lo que este intervalo será aproximadamente de 0-3.3 V. Ya que esta DAC no puede dar salidas de voltaje negativo el valor para signed sera '0'. Con esta información podemos encontrar los valores para establecer nuestro formato. Podemos obtenerlos mediante este código de Matlab:
minimo = 0; %Volts
maximo = 3.3; % Volts
is_signed = false;
word_length = 10;
[E_min, E_max] = range(fi([], is_signed, word_length, 0));
A = double ([E_min, 1; E_max, 1]);
b = double ([minimo; maximo]);
x = A\b;
format long g
slope = x(1)
bias = x(2)
La ejecución del código nos da una pendiente s = 0.0032258064516129 y un bias b = 0. La señal para este ejemplo tendrá una frecuencia de 0.05 Hz y un periodo de muestreo de 1 segundo (si pueden usar un osciloscopio pueden probar con una frecuencia más alta y un periodo de muestro más corto). Generaremos un código VHDL con HDL Coder a partir de este modelo de Simulink:
Los subsitemas que he llamado Fixed-Point Multiplication contienene la implementación de la multiplicación que hemos descrito aquí. Dado que b = 0 la multiplicación de punto fijo simplemente se define como el producto de los enteros almacenados y la pendiente s:
Para generar el código nos vamos a Code > HDL Code > Generate HDL. Para la comunicación SPI con la LTC1661 he modificado un código que había publicado anteriormente. Los módulos son los siguiente:
- Divisor
- DAC_SPI
- Generador_Coseno_HDL (Generado por HDL Coder)
Nota: durante la generación del código se indica que el reloj para el modulo del generador sea el doble del establecido. En este caso el divisor tiene una salida de 2 Hz.
Según la simulación, y si implementamos correctamente las cosas, las secuencia de voltaje que deberíamos ver experimentalmente a la salida del DAC es la siguiente:
Aquí muestro un video donde se puede ver que no tengo un buen multímetro y tuve que aumentar a 4 segundos el periodo de muestreo para dar tiempo de estabilizar las lecturas:
Referencias
Compute Slope and Bias, MathWorks
Fixed-Point: Rage and Precision, MathWorks
Etiquetas:
FPGA's,
Matemáticas,
Matlab,
Simulink,
VHDL
jueves, 16 de noviembre de 2017
Multiplicación en punto fijo [formato fixdt(signed,n,l)]
Aunque al principio pueda chocar con la intuición, es posible realizar de forma digital una multiplicación de dos números con parte entera y decimal utilizando únicamente números enteros. De hecho, para aplicaciones de instrumentación científica o simulaciones numéricas dónde la precisión sea la prioridad es incluso aconsejable evitar el uso del famoso punto flotante. Recomiendo enormemente ver este capítulo del canal PBS Infinite Series para entender desde el punto de vista matemático el problema fundamental de los tipos double y single. En pocas palabras este problema radica en la en las limitaciones en la accesibilidad sobre la recta numérica computable. La resolución del punto variable no es homogénea a lo largo de todo su rango. Una segunda limitación de las operaciones de punto flotante tiene que ver a su costo de implementación en hardware; construir circuitos digitales de punto flotante consumen más recursos en un FPGA.
El formato de punto fijo con el trabajaremos en esta entrada es:
Los parámetros n y signed nos da el rango del formato:
A partir de la relación entre el Valor del Mundo Real (como se le llama en la jerga de punto fijo y denotaremos como Val) y el entero almacenado (que denotaremos por E) podemos definir la multiplicación de punto fijo de la siguiente forma:
Esto significa que la multiplicación se realiza como el producto de dos enteros mas un bitshift a la derecha de l locaciones. Para comprender mejor este formato realizaremos un ejemplo práctico concreto. Supongamos que queremos digitalizar una señal de voltaje sinusoidal de amplitud de 2 V con una frecuencia de 1 Hz mediante un ADC de 12 bits de resolución y un rango de entrada de 0-4 V. Supongamos que una vez digitalizada la señal queremos realizar un escalamiento de 0.321 y enviar esta nueva señal a un DAC de la misma resolución y rango. ¿Cómo resolveríamos el problema sin utilizar operaciones de punto flotante?
Bien, usaremos el formato fixdt(signed,n,l) para este ejemplo. Lo primero que debemos hacer es establecer los parámetros del formato. Ya que nuestro ADC no puede leer voltajes negativos, el parámetro signed será igual a 0. Dado que la resolución es de 12 bits entonces n = 12. Sabiendo que el voltaje máximo de lectura es 4 V encontramos el valor l de la siguiente manera:
Tenemos que el valor de l es 10 dando un rango de 0-3.99 V. Entonces el formato para nuestro ejemplo queda definido así: fixdt(0,12,10). El siguiente paso en convertir la ganancia de 0.321 en formato fixdt(0,12,10):
El equivalente de la ganancia resulta ser: 329. Podemos realizar ahora una simulación de nuestro ejemplo en Simulink y comparar el desempeño de la operación de punto fijo contra la de punto flotante:
La simulación del módulo mostrando algunos valores de ejemplo para los enteros almacenados de entrada y salida es la siguiente:
El formato de punto fijo con el trabajaremos en esta entrada es:
fixdt(signed,n,l)
- signed : toma el valor de 1 si el entero almacenado es signado y 0 en caso contrario
- n : longitud en bits del entero almacenado
- l : longitud en bits de la parte fraccional
Los parámetros n y signed nos da el rango del formato:
A partir de la relación entre el Valor del Mundo Real (como se le llama en la jerga de punto fijo y denotaremos como Val) y el entero almacenado (que denotaremos por E) podemos definir la multiplicación de punto fijo de la siguiente forma:
Esto significa que la multiplicación se realiza como el producto de dos enteros mas un bitshift a la derecha de l locaciones. Para comprender mejor este formato realizaremos un ejemplo práctico concreto. Supongamos que queremos digitalizar una señal de voltaje sinusoidal de amplitud de 2 V con una frecuencia de 1 Hz mediante un ADC de 12 bits de resolución y un rango de entrada de 0-4 V. Supongamos que una vez digitalizada la señal queremos realizar un escalamiento de 0.321 y enviar esta nueva señal a un DAC de la misma resolución y rango. ¿Cómo resolveríamos el problema sin utilizar operaciones de punto flotante?
Bien, usaremos el formato fixdt(signed,n,l) para este ejemplo. Lo primero que debemos hacer es establecer los parámetros del formato. Ya que nuestro ADC no puede leer voltajes negativos, el parámetro signed será igual a 0. Dado que la resolución es de 12 bits entonces n = 12. Sabiendo que el voltaje máximo de lectura es 4 V encontramos el valor l de la siguiente manera:
Tenemos que el valor de l es 10 dando un rango de 0-3.99 V. Entonces el formato para nuestro ejemplo queda definido así: fixdt(0,12,10). El siguiente paso en convertir la ganancia de 0.321 en formato fixdt(0,12,10):
Click para agrandar
El opam en el circuito añade el offset necesario para levantar la señal de la fuente y pueda ser adquirida por el ADC. Los bloques de ADC y DAC son simplemente subsistemas que realizan las operaciones de conversión de decimal a fixdt(1,12,10) y de muestro ZOH. Si bien Matlab/Simulink permite realizar las conversiones al formato fixdt(signed,n,l), decidí manejar uint16 para poder generalizar el ejemplo para ser implementado en microcontroladores y no sólo en FPGA's. Nuestro formato fixdt(0,12,10) cabe en un uint16 y eso también permite hacer una intuición más clara de que simplemente trabajamos con formatos enteros convencionales. La gráfica resultante con la comparativa es la siguiente:
Vemos que la correspondencia entre las operaciones de punto fijo y punto flotante son muy buenas. Podemos ahora a aventurarnos a escribir un codigo VHDL para nuestra operación de escalamiento de este ejemplo:
Click para agrandar
Para finalizar quisiera hacer un último comentario. Quizá notaron que tuvimos mucha suerte al calcular el valor del exponente l y nos dio un número que se ajustaba perfectamente al rango del ADC. No fue suerte, yo elegí el rango para que este ejemplo fuera más sencillo. En la practica los DAC's suelen tener rangos de 0-5.2 V o 0-3.3 V. ¿Que se hace en esos casos? Aquí es donde entra un nuevo formato: fixdt(signed,n,s,b). Pero hablaremos de él en otra entrada.
Referencias:
Fixed-Point: Rage and Precision, MathWorks
Etiquetas:
FPGA's,
Matemáticas,
Matlab,
Simulink,
VHDL
martes, 7 de noviembre de 2017
Convertir un modelo de Simulink a código VHDL con HDL Coder [Ejemplo 2]
En una entrada anterior exponía el como generar un código VHDL de una función booleana sencilla. Este será también un ejemplo sencillo pero mucho más interesante. Generaremos un controlador P para la posición de un motor DC con caja de engranes y encoder [este modelo en particular] alimentado por un driver Pololu Qik 2s12v10. El esquema del sistema es el siguiente:
La posición del motor será leída mediante el encoder que viene integrado en el motor. El modelo de Simulink que convertiremos a código VHDL es el siguiente:
Bloques requeridos:
HDL Coder > Commonly Used Blocks > In1
HDL Coder > Commonly Used Blocks > Out1
HDL Coder > Sources > Constant
HDL Coder > Math Operations > Divide
HDL Coder > Discontinuities > Saturation
HDL Coder > Signal Attributes > Data Type Conversion
Diagrama:
La operación de división por números que no sean potencias de 2 no está disponible en todos los dispositivos por lo que en este caso usaremos 1/32 como aproximación de 0.03 [es posible implementar en hardware divisiones entre números arbitrarios pero serán tratados en otro ejemplo]. Es necesario establecer en todos los bloques el tipo de dato int16 dando doble click a cada bloque y cambiando el tipo de dato la siguiente forma:
También sera necesario dar doble click en el bloque Divide, ir a Signal Attributes y activar el modo "Saturate on integer overflow" y verificar que el modo de redondeo sea "zero" o "simplest". Habiendo hecho todo lo anterior generemos el código VHDL yéndonos a la pestaña Code > HDL Code > Generate HDL. Les generará el siguiente código:
Para complementar el controlador usaremos los siguientes módulos VHDL que ya he publicado por acá:
Agregando todos los archivos .vhd requeridos al proyecto se puede proceder a conectar los bloques de forma esquemática en ISE de la siguiente manera:
Si no están familiarizados con la creación de proyectos esquemáticos en ISE aquí hay un video-tutorial en español muy bien explicado [utilizan Verilog pero el procedimiento es el mismo]. El bloque selector es simplemente una declaración when que envía una referencia de 0 si el switch esta apagado y de 2400 (90° en cuentas de encoder) cuando esta encendido. En este video muestro el funcionamiento del sistema implementado en una tarjeta Basys2:
La posición del motor será leída mediante el encoder que viene integrado en el motor. El modelo de Simulink que convertiremos a código VHDL es el siguiente:
Bloques requeridos:
HDL Coder > Commonly Used Blocks > In1
HDL Coder > Commonly Used Blocks > Out1
HDL Coder > Sources > Constant
HDL Coder > Math Operations > Divide
HDL Coder > Discontinuities > Saturation
HDL Coder > Signal Attributes > Data Type Conversion
Diagrama:
La operación de división por números que no sean potencias de 2 no está disponible en todos los dispositivos por lo que en este caso usaremos 1/32 como aproximación de 0.03 [es posible implementar en hardware divisiones entre números arbitrarios pero serán tratados en otro ejemplo]. Es necesario establecer en todos los bloques el tipo de dato int16 dando doble click a cada bloque y cambiando el tipo de dato la siguiente forma:
También sera necesario dar doble click en el bloque Divide, ir a Signal Attributes y activar el modo "Saturate on integer overflow" y verificar que el modo de redondeo sea "zero" o "simplest". Habiendo hecho todo lo anterior generemos el código VHDL yéndonos a la pestaña Code > HDL Code > Generate HDL. Les generará el siguiente código:
Para complementar el controlador usaremos los siguientes módulos VHDL que ya he publicado por acá:
Agregando todos los archivos .vhd requeridos al proyecto se puede proceder a conectar los bloques de forma esquemática en ISE de la siguiente manera:
domingo, 5 de noviembre de 2017
Modoulo VHDL para un encoder de cuadratura en modo X4
El código de este módulo es sólo una modificación del código de Dr Dew (Scilab Ninja). La única diferencia es que le he agregado el conteo de ascenso y descenso para un registro de 16 bits. National Instrumentes tiene una nota en español muy completa sobre el manejo de encoders de cuadratura. La ventaja del modo X4 es que permite tener la máxima resolución angular posible para un encoder.
martes, 31 de octubre de 2017
Módulo de control para un Qik 2s12v10 de Pololu en VHDL
Estoy empezando a aprender el HDL Coder Toolbox de Matlab/Simulink pero antes de empezar hacer pruebas con controladores PID en hardware necesitaba escribir el código de un modulo que me permitiera controlar el driver para motores DC Qik 2s12v10 que había comprado para mi montura altazimutal. El módulo esta diseñado para trabajar junto con este módulo UART del que había hablado en la entrada pasada. El esquema de la entidad del modulo es la siguiente:
Esta versión del módulo solo puede enviar los valores de potencia en 7-bits signados y no puede leer la corriente que consumen los motores ni los mensajes de error. Espero en un futuro cree un módulo más completo. Pero la funcionalidad de esta versión es suficiente para realizar muchos proyectos. El siguiente diagrama muestra la implementación esquemática para una tarjeta Basys2 en dónde probé el módulo:
Aquí muestro en un video el funcionamiento del módulo:
domingo, 29 de octubre de 2017
Módulo UART en VHDL
Este módulo fue por el profesor Carlos García Lucero de la FCE-BUAP. Aunque debe estar disponible en la página de INTESC considero útil repostearlo por aquí para que tenga mejor accesibilidad para los buscadores. El código esta muy bien comentado y está implementado en un solo archivo vhd.
domingo, 15 de octubre de 2017
Convertir un modelo de Simulink a código VHDL con HDL Coder [Ejemplo 1]
Para este primer ejemplo, generarémos el código VHDL para una función booleana sencilla de la forma X = AB xor C desde un diagrama en Simulink.
Bloques requeridos
HDL Coder > Commonly Used Blocks > In1
HDL Coder > Commonly Used Blocks > Out1
HDL Coder > Logic and Bit Operations > Logical Operators
Diagrama
Una vez terminado el bloque debemos irnos a la pestaña Code > HDL Code > Generate HDL. Si no hay ningún error deberá aparecer lo siguiente en la ventana de comandos:
### Generating HDL for 'Ejemplo_HDL'.
### Starting HDL check.
### Begin VHDL Code Generation for 'Ejemplo_HDL'.
### Working on Ejemplo_HDL as hdlsrc\Ejemplo_HDL\Ejemplo_HDL.vhd.
### Creating HDL Code Generation Check Report Ejemplo_HDL_report.html
### HDL check for 'Ejemplo_HDL' complete with 0 errors, 3 warnings, and 0 messages.
### HDL code generation complete.
Por defecto el código generado aparecerá en el directorio hdlsrc\. El código generado para este ejemplo es el siguiente:
Bloques requeridos
HDL Coder > Commonly Used Blocks > In1
HDL Coder > Commonly Used Blocks > Out1
HDL Coder > Logic and Bit Operations > Logical Operators
Diagrama
Una vez terminado el bloque debemos irnos a la pestaña Code > HDL Code > Generate HDL. Si no hay ningún error deberá aparecer lo siguiente en la ventana de comandos:
### Generating HDL for 'Ejemplo_HDL'.
### Starting HDL check.
### Begin VHDL Code Generation for 'Ejemplo_HDL'.
### Working on Ejemplo_HDL as hdlsrc\Ejemplo_HDL\Ejemplo_HDL.vhd.
### Creating HDL Code Generation Check Report Ejemplo_HDL_report.html
### HDL check for 'Ejemplo_HDL' complete with 0 errors, 3 warnings, and 0 messages.
### HDL code generation complete.
Por defecto el código generado aparecerá en el directorio hdlsrc\. El código generado para este ejemplo es el siguiente:
miércoles, 15 de julio de 2015
Comunicación SPI con un DAC en VHDL
De la hoja de especificaciones, tenemos el siguiente diagrama de tiempos:
Los periodos que nos interesan son t5, que es el periodo mínimo entre datos ( el tiempo mínimo que el FPGA debe esperar para enviar el siguiente dato de manera que el DAC pueda reaccionar) y t3 y t4 que nos indican que SCK debe tener un duty cycle de 50% con un periodo T = t3+t4. En la página 4 de la hoja podemos encontrar que los tiempos mínimos requeridos son: t5 = 100 ns y una frecuencia para SCK de 10 Mhz. Los periodos t1 y t2 nos indican una especificación igual de importante. Indican que el flanco de subida de SCK debe ocurrir justo a la mitad del bit enviado. Entendiendo estas restricciones, podemos ahora comenzar a escribir nuestro programa. Nuestra entidad deberá tener 2 entradas: clk y un reset, y 3 salidas: SCK, CD y datos:
La implementación estará formada por los siguientes bloques elementales:
- Divisor de frecuencia (50 a 10 MHz)
- Divisor de frecuencia para reloj de CS
- Memoria ROM
- Registro PISO (Parallel Input Serial Output)
- Generador de reloj SCK
Memoria ROM
Los datos almacenados por esta memoria deben tener la estructura especificada por el fabricante (pag. 8), que en este caso es la siguiente:
A3A2A1A0D9D8D7D6D5D4D3D2D1D0X1X0
Control Datos Don't Care
Para este ejemplo debemos enviar la mitad del voltaje de referencia al canal A y actualizar su salida, por lo que los valores para el código de control deben ser "1001" (ver tabla de la pag. 9). Se crean entonces las siguientes señales que serán usadas en la memoria ROM:
type rom is array(0 to 2) of std_logic_vector(15 downto 0);
constant control: std_logic_vector(3 downto 0):="1001";
constant dc: std_logic_vector(1 downto 0):= "00"; -- Bits don't care constant myrom: rom
:=( control&"0111111111"&dc,
control&"0111111111"&dc,
control&"0111111111"&dc);
La razón de implementar una memoria ROM como un arreglo de varios elementos para un ejemplo tan simple es para que pueda ser modificada fácilmente para hacer cosas más interesantes como enviar una función triangular o cosenoidal (en cuyo caso habrá que incrementar el tamaño del array a entre 50 o 100 elementos). Otro detalle que debo mencionar es que, como se verá en el programa completo, el incremento de la dirección se hace con el reloj CS. Esto se hace a modo de tener el bit-rate máximo. Se puede agregar un nuevo divisor a una frecuencia más baja para cambiar el dato si así lo desean.
Registro PISO
Este registro serializará los datos recibidos de la salida de la memoria ROM y los enviará de forma síncrona en cada flanco de bajada* del reloj secundario de 10 MHz. La serialización únicamente se efectuará después de recibir un flanco de subida del reloj CS.
*Esto de modo que se cumpla la especificación de los tiempos t1 y t2, así SCK deberá estar en fase con el reloj secundario.
Generador de reloj SCK
Este reloj deberá tener una frecuencia de 10 MHz pero requiere cumplir una condición: debe activarse durante el siguiente flanco de subida del reloj secundario (10 MHz) después del flanco de bajada del pulso CS. Esto se logra mediante una implementación if síncrona normal. Sin embargo, dado que no es posible implementar físicamente una instrucción en flanco de bajada inmediatamente después de un flanco de subida ("bad synchronous description") se recuré a un truco combinacional para generar el reloj SCK que puede verse en el programa completo.
Teniendo el programa listo, así luce en simulación con ISim:
(Click para agrandar)
Programa completo
LIBRARY IEEE;
USE IEEE.STD_LOGIC_1164.ALL;
USE IEEE.STD_LOGIC_ARITH.ALL;
USE IEEE.STD_LOGIC_UNSIGNED.ALL;
entity DAC_SPI is
port(
clk : in std_logic; -- 50 MHz
reset : in std_logic;
CS : out std_logic;
SCK : out std_logic; -- 10 MHz
datos : out std_logic); -- Salida serial
end entity;
architecture rtl of DAC_SPI is
-- Señales
-- Divisor
signal count: integer :=1;
signal clk_10: std_logic:='0';
-- ROM
signal address: integer range 0 to 2;
type rom is array(0 to 2) of std_logic_vector(15 downto 0);
constant control: std_logic_vector(3 downto 0):="1001"; -- Cargar y actualizar canal A
constant dc: std_logic_vector(1 downto 0):= "00"; -- Bits don't care
constant myrom: rom
:=( control&"0111111111"&dc,
control&"0111111111"&dc,
control&"0111111111"&dc);
signal rom_out: std_logic_vector(15 downto 0):=(others=>'0');
signal clk_cs: std_logic:='0';
-- PISO
signal temp: std_logic_vector(15 downto 0):= (others =>'0');
signal t: std_logic;
-- Otros
signal a: integer range 0 to 18 :=1;
signal sck_t: std_logic;
--
begin
-- Divisor de frecuencia (10 MHz)
process(clk) begin
if(clk'event and clk='1') then
count <=count+1;
if(count = 5) then -- dónde count = frec de reloj / frec deseada
clk_10 <= not clk_10;
count <=1;
end if;
end if;
end process;
--
-- PISO
process (clk_10,rom_out,clk_cs)begin
if (CLK_10'event and CLK_10='0') then
if (clk_cs='1') then
temp(15 downto 0) <= rom_out(15 downto 0);
else
t <= temp(15);
temp(15 downto 1) <= temp(14 downto 0);
temp(0) <= '0';
end if;
end if;
end process;
datos <= t;
--
-- SCK
process(clk_10,clk_cs) begin
if (clk_10'event and clk_10 = '1') then
if(clk_cs='0')then
SCK_t <= '1';
else
SCK_t <= '0';
end if;
end if;
end process;
SCK <= (sck_t) and (clk_10) and not(clk_cs);
--
-- Divisor de frecuencia para CLK_CS
process(clk_10) begin
if(clk_10'event and clk_10='1') then
a <=a+1;
if(a = 17) then
clk_cs <= '1';
a <=0;
else
clk_cs <= '0';
end if;
end if;
end process;
--
-- ROM
process(clk_cs,reset) begin
if reset = '1' then
address <= 0;
elsif( clk_cs'event and clk_cs = '1' ) then
if address < 2 then
address <= address + 1;
else
address <= 0;
end if;
rom_out <= myrom(address);
end if;
end process;
-- CS
CS <= clk_cs;
--
end rtl;
jueves, 26 de junio de 2014
Problema al implementar un programa asíncrono en VHDL
Supongamos que tenemos un programa muy simple en VHDL en el que prendemos y apagamos dos leds mediante dos interruptores:
library ieee;
use ieee.std_logic_1164.all;
entity leds is
port( a,b : in std_logic;
s1,s2: out std_logic);
end leds;
architecture control of leds is
begin
s1 <= a;
s2 <= b;
end control;
Para la tarjeta de desarrollo Basys2 nuestro archivo .ucf quedaría:
# Interruptores
NET "a" LOC = P11;
NET "b" LOC = L3;
# Leds
NET "s1" LOC = M5;
NET "s2" LOC = M11;
Hasta aquí todo parece en orden e incluso nuestro programa funciona bien en la simulación. Pero un problema muy común entre los estudiantes de los primeros cursos de sistemas digitales es que al momento de cargar el archivo .bit de un programa asíncrono como este a una tarjeta de desarrollo, ésta se queda sin hacer nada. Esto ocurre porque la tarjeta espera arrancar por default con el oscilador interno aunque no lo usemos en el programa (razón por la que este problema muy rara vez se presenta en programas que si lo usan). Este problema se resuelve simplemente haciendo click derecho sobre "Generate Program File", buscar "Process Properties" que nos abrirá la siguiente ventana en la debemos buscar la categoría "Startup Options" y cambiar la opción de FPGA Start-Up Clock de CCLK a JTAG Clock:
library ieee;
use ieee.std_logic_1164.all;
entity leds is
port( a,b : in std_logic;
s1,s2: out std_logic);
end leds;
architecture control of leds is
begin
s1 <= a;
s2 <= b;
end control;
Para la tarjeta de desarrollo Basys2 nuestro archivo .ucf quedaría:
# Interruptores
NET "a" LOC = P11;
NET "b" LOC = L3;
# Leds
NET "s1" LOC = M5;
NET "s2" LOC = M11;
Hasta aquí todo parece en orden e incluso nuestro programa funciona bien en la simulación. Pero un problema muy común entre los estudiantes de los primeros cursos de sistemas digitales es que al momento de cargar el archivo .bit de un programa asíncrono como este a una tarjeta de desarrollo, ésta se queda sin hacer nada. Esto ocurre porque la tarjeta espera arrancar por default con el oscilador interno aunque no lo usemos en el programa (razón por la que este problema muy rara vez se presenta en programas que si lo usan). Este problema se resuelve simplemente haciendo click derecho sobre "Generate Program File", buscar "Process Properties" que nos abrirá la siguiente ventana en la debemos buscar la categoría "Startup Options" y cambiar la opción de FPGA Start-Up Clock de CCLK a JTAG Clock:
sábado, 23 de febrero de 2013
Convertidor Binario-BCD con displays en VHDL
Si consultaste Google, seguramente encontraste la función to_bcd()del blog VHDL Guru pero quizá te queden dudas sobre como implementarla en un código completo (Si, también tuve el mismo problema). Bien, en el caso del compilador ISE de Xilinx (se observará que estoy usando una tarjeta Basys 2) debe agregarse un archivo de paquete que incluya esta función. Esto puede hacerse usando la plantilla VHDL Package o creando simplemente un nuevo archivo .vhd en blanco y agregarlo al proyecto (El compilador detectará automáticamente que es un paquete al terminar de escribir el código).
El siguiente código es la correcta implementación del Paquete y Cuerpo de Paquete de manera que la función pueda ser utilizada en nuestro programa:
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;
package bcd is
function to_bcd (bin : std_logic_vector(7 downto 0)) return std_logic_vector;
end bcd;
package body bcd is
function to_bcd (bin : std_logic_vector(7 downto 0)) return std_logic_vector is
variable i : integer:=0;
variable bcd : std_logic_vector(11 downto 0) := (others => '0');
variable bint : std_logic_vector(7 downto 0) := bin;
begin
for i in 0 to 7 loop
bcd(11 downto 1) := bcd(10 downto 0);
bcd(0) := bint(7);
bint(7 downto 1) := bint(6 downto 0);
bint(0) :='0';
if(i < 7 and bcd(3 downto 0) > "0100") then
bcd(3 downto 0) := bcd(3 downto 0) + "0011";
end if;
if(i < 7 and bcd(7 downto 4) > "0100") then
bcd(7 downto 4) := bcd(7 downto 4) + "0011";
end if;
if(i < 7 and bcd(11 downto 8) > "0100") then
bcd(11 downto 8) := bcd(11 downto 8) + "0011";
end if;
end loop;
return bcd;
end to_bcd;
end bcd;
Al guardar el código agregado al proyecto, la función se agregará automáticamente a la librería "work". De manera que habrá que usarla en nuestro programa:
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;
library work;
use work.bcd.all; -- Aquí está nuestra función
entity display is
port ( salida : out std_logic_vector(7 downto 0);
selector: out std_logic_vector(3 downto 0);
binario : in std_logic_vector(7 downto 0);
clk : in std_logic );
end entity;
architecture mostrar of display is
signal a: std_logic_vector(1 downto 0) :=(others => '0');
signal PULSO: STD_LOGIC:='0';
constant SESENTA: integer :=99999;
signal RET: integer range 0 to SESENTA:=0;
signal BCD_out: std_logic_vector(11 downto 0);
signal bcd: std_logic_vector(3 downto 0);
begin
RETARDO: PROCESS(CLK) -- Divisor de frecuencia (de 50MHz a 50Hz)
BEGIN
IF (RISING_EDGE(CLK)) THEN
PULSO<='0';
RET<=RET+1;
IF RET=SESENTA THEN
RET<=0;
PULSO<='1';
END IF;
END IF;
END PROCESS;
process(pulso) begin -- Contador para selectores
if (pulso'event and pulso = '1') then
a <= a + 1;
end if;
end process;
BCD_out <= to_bcd(binario); -- Llamado a la función to_bcd
process (bcd) -- Decodificador BCD a 7 segmentos
begin
case bcd is
when "0000"=> salida <="00000011"; -- '0'
when "0001"=> salida <="10011111"; -- '1'
when "0010"=> salida <="00100101"; -- '2'
when "0011"=> salida <="00001101"; -- '3'
when "0100"=> salida <="10011001"; -- '4'
when "0101"=> salida <="01001001"; -- '5'
when "0110"=> salida <="01000001"; -- '6'
when "0111"=> salida <="00011111"; -- '7'
when "1000"=> salida <="00000001"; -- '8'
when "1001"=> salida <="00001001"; -- '9'
when others=> salida <="11111111"; -- 'x'
end case;
end process;
process (a) begin -- Ciclo de selectores
case a is
when "00" => bcd <= BCD_out(3 downto 0);
selector <= "0111";
when "01" => bcd <= BCD_out(7 downto 4);
selector <= "1011";
when "10" => bcd <= BCD_out(11 downto 8);
selector <= "1101";
when others => bcd <= "0000";
selector <= "1111";
end case;
end process;
end architecture;
miércoles, 16 de enero de 2013
Generador de pulsos de 1Hz en VHDL (pulso por segundo)
library ieee;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;
entity segundo is
port ( pulso : inout std_logic:='0';
clk : in std_logic);
end entity;
architecture divisor of segundo is
signal count : integer :=1;
begin
process(clk) begin
if(clk'event and clk='1') then
count <=count+1;
if(count = 50000000) then --count = frec de reloj/frec deseada
PULSO <= not PULSO;
count <=1;
end if;
end if;
end process;
end architecture;
use ieee.std_logic_1164.all;
use ieee.std_logic_arith.all;
use ieee.std_logic_unsigned.all;
entity segundo is
port ( pulso : inout std_logic:='0';
clk : in std_logic);
end entity;
architecture divisor of segundo is
signal count : integer :=1;
begin
process(clk) begin
if(clk'event and clk='1') then
count <=count+1;
if(count = 50000000) then --count = frec de reloj/frec deseada
PULSO <= not PULSO;
count <=1;
end if;
end if;
end process;
end architecture;
Nota: Variando el valor de count, se puede utilizar el componente como divisor de frecuencia y obtener cualquier frecuencia menor a la del reloj que se esté usando.
viernes, 30 de septiembre de 2011
Mi nuevo bebé
La empresa Xilinx tiene un programa universitario con el que hacen descuentos muy buenos a estudiantes. Compré mi propio FPGA con tarjeta de desarrollo (BASYS 2 Spartan-3E) por $49 USD. Claro, más $29 USD de envío (seleccioné UPS express por error ) y $110 pesos de pinches impuestos aduanales. Llegó el miércoles desde Pullman, WA, después de 3 días de viaje, aunque la recibí hasta hoy en la tarde por haberme ausentado en las entregas pasadas; fui de visita a una central nuclear pero eso va para otra entrada.
Suscribirse a:
Entradas (Atom)






























