Mostrando entradas con la etiqueta FPGA's. Mostrar todas las entradas
Mostrando entradas con la etiqueta FPGA's. Mostrar todas las entradas

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:
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.
En esta convención un número con parte entera y decimal se representa de la siguiente manera:

 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:
El esquemático del sistema es el 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 

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:

 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
En esta convención un número con parte entera y decimal se representa de la siguiente manera:
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:
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:

La simulación del módulo mostrando algunos valores de ejemplo para los enteros almacenados de entrada y salida es la siguiente:
 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:

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:

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:

martes, 1 de marzo de 2016

Programa de ejemplo en C para MicroBlaze

 
(Tarjeta de desarrollo Symbhia)

#include <xparameters.h>
#include <xgpio.h>

XGpio LEDS;
XGpio SW;

u8 sws; // Variable tipo uint8
int i;

int main(){
//Inicialización
  XGpio_Initialize(&LEDS,XPAR_GPIO_1_DEVICE_ID);
  XGpio_Initialize(&SW,XPAR_GPIO_0_DEVICE_ID);

while(1){
//Lectura de switches
  sws = XGpio_DiscreteRead(&SW,1); //(puntero de instancia,canal)

//Escritura de la variable sws en LEDS
  XGpio_DiscreteWrite(&LEDS,1,sws); // (puntero de instancia,canal,dato)
   }

return 0;

}

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;


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.