VU emu | |
Introducción Usando el emulador Código Fuente del VU emu Por hacer Downloads
|
|
Introducción VU emu es un emulador de la unidad vectorial VU1 de la PS2 desarrollado en C++. La intención principal con la que se ha hecho es la de poder probar pequeñas rutinas en micro código antes de compilarlas y ejecutarlas en la PS2. Si dispones de un Kit Linux para PS2 puedes tirar esto a la basura y usar directamente el Sauce's Visual VU Debugger que hace más cosas que este programa y mejor. Pero para los pobres que no tenemos el Kit de desarrollo Linux y queremos hacer nuestros programitas con el compilador GNU esta herramienta puede ser útil. En realidad me he metido a hacer esto por que, literalmente, me tocaba los webetes que todas las herramientas interesantes del Kit Linux (VCL, Sauces, etc...) no se den en código abierto. Esta versión es la beta betísima 0.1 y debe tener cientos de bugs pero prometo ir depurándolo poco a poco. Inicié esto como una distracción de un par de horas y, al final, me ha llevado semanas terminarlo. Estoy un poco saturado de aplicación así que no la he probado a fondo. Cualquier error que encontréis enviádmelo a m3ntol@yahoo.es para que pueda arreglarlo. Y finalmente, ¿por qué está en inglés? pues porque me da una pereza terrible traducir un programa terminado. Así que los hago ya traducidos, si sabes programar la VU1 seguro que sabes inglés.
|
|
Usando el emulador Para probar las rutinas en el emulador debemos preparar un poco el código. En primer lugar deberemos tener en ficheros separados el micro código y los datos. El fichero del código no debe tener sentencias del preprocesador tan solo admite micro código y etiquetas. Comenzará a ejecutar el código por la primera línea y terminará cuando encuentre un NOP[E] en una UPPER. Para haceros una idea más clara del formato de los ficheros de código he incluido numerosos ejemplos en los que se podrá ver como funciona el emulador. La pantalla del emulador tiene los siguientes elementos, haz click sobre ellos para obtener una descripción:
|
Cargar programa
Este botón abrirá un dialogo 'abrir fichero' en el que seleccionaremos el
fichero con el código del programa, recordad, nada de memoria. Si encuentra
algún error avisará con una ventana de 'Sintax Error'. Por favor, si esto ocurre
tened la amabilidad de enviarme el fichero a
m3ntol@yahoo.es para que pueda revisar el error y arreglarlo en futuras
versiones.
Cargar memoria
Este botón abre el diálogo de carga de ficheros para leer los datos de memoria
pero, de momento, no hace nada. En la próxima versión lo hará pero en esta hay
que meter los datos de memoria 'a mano' en la ventana de memoria.
Grabar programa
Este botón tampoco hace nada :( en la próxima versión lo hará, prometido ;)
RESET
El botón de reset borra todos los datos de la memoria, borra el programa y
resetea registros y flags. Es como cerrar la aplicación y volver a abrirla.
RESTART
Una vez cargado un programa, el botón de restart reinicializará la memoria,
todos los registros y flags. Es como apagar la aplicación, volver a ejecutarla y
cargar el fichero de código.
Ejecutar instrucción
Este botón ejecutará la instrucción en curso, la que esté apuntada por el
Program Counter. Se ejecuta primero la instrucción UPPER u luego la LOWER,
realizando los pertinentes cambios en registros, memoria y flags.
Run to BreakPoint
Si no queremos ir paso a paso ejecutando instrucciones podemos establecer un
breack point y darle a este botón. Con ello se ejecutarán automáticamente todas
las instrucciones hasta que se encuentre una con BreakPoint.
Settings
En el futuro configurará algunas opciones, de momento no hace nada.
Salir
Cierra la aplicación y retorna a windows.
Puntero de instrucción.
Esta columna indica qué instrucción es la que va a ejecutarse. Ello se indica
mediante un símbolo '>'.
Break Points
Esta columna indica dónde se encuentran los breakpoints. Cuando una instrucción
tiene un breakpoint apareceré con un 'O'. Para poner un breakpoint debemos hacer
click con el botón derecho del ratón sobre el código (upper o lower) de la línea
que queramos. Para quitar un breackpoint volvemos a hacer click con el botón
derecho. OJO!! si pones breakpoints en las etiquetas pueden producirse errores.
Dirección de memoria
Indica la dirección en la que está alojada la instrucción (dirección x 16)
Ciclos
Indica el número de ciclos de reloj que consumió la última ejecución de la
instrucción. Hay que tener en cuenta que por la arquitectura propia de la VU1 se
pueden producir retardos (stall) en los accesos a datos etc. Todo ello queda
debidamente contabilizado en este número.
UPPER instruction
El microcódigo correspondiente a la parte UPPER de la línea.
LOWER instruction
El microcódigo correspindiente a la parte LOWER de la línea.
Además de la ventana principal también existe la ventana de la derecha donde se puede acceder a información muy interesante sobre los registros. Haz click sobre los elementos de la imagen para acceder a una descripción:
Selector de registro
Con este menú desplegable seleccionamos el registro que queremos ver/editar. Hay
16 registros VI, 32 revistos VF y un ACC, P, Q y R.
Valores del registro
En estas cajas de edición veremos los valores x,y,z,w de los registros VF y el
valor único de los VI. También podremos editar esos valores y poner los que nos
apetezca.
STALL
Si el registro aún no ha sido fijado en la memoria, indica el número de ciclos
que necesita para ello. Si intentamos leer una instrucción con stall distinto de
cero se producirán ciclos de espera.
Ultima instrucción que lo leyó
Dirección de la última instrucción que accedió a este registro para leerlo.
Ultima instrucción que lo escribió
Dirección de la última instrucción que accedió a este registro para escribir sus
valores.
Selector de visualización
Los valores de los registros VF se pueden almacenar en distintos formatos, este
util selector permitirá verlos con el formato que queramos.
Las ventanas de Instr. Status y flags son autoexplicativas, en una veremos información 'a granel' sobre la instrucción a ejecutar y el la otra los valores de los flags. OJO!! solo podemos ver los flags, no podemos editarlos.
Por último, tenemos una ventana con la memoria de la unidad VU1 en la que podremos ver y editar su contenido, También tiene un selector con el que podremos elegir entre verla como enteros, floats o enteros en hexadecimal.
Si estás interesado en el código fuente del emulador puedes descargarlo aquí, la aplicación contiene una serie de clases que emulan a la VU1 escritas en C++ estándar y un front escrito en C++ Builder 4. Se puede poner el front que se quiera (siempre que se programe claro) y añadir funcionalidad como, por ejemplo, poder editar los flags, o, simplemente migrarlo a Linux.
La parte interesante es la que emula a la VU1, existe una clase principal 'VU' con contiene todos los elementos de la unidad vectorial (registros, flags) y funcionalidad para ejecutar los programas. El código está documentado (no muy bien pero algo) y se puede cambiar a gusto del consumidor.
Seguro que existen multitud de bugs que intentaré ir puliendo a medida que pruebe la aplicación. si encuentras alguno y lo arreglas, por favor, remíteme el código. Y si encuentras alguno y no lo arreglas, por favor, remíteme el bug para que yo pueda arreglarlo.
Copio las clases de las VUs con algún comentario para que os podáis hacer una idea de como está planteado:
//=============== // definiciones de tipos //=============== typedef unsigned char uint8; typedef unsigned short uint16; typedef unsigned int uint32; typedef unsigned long uint64; typedef char int8; typedef short int16; typedef int int32; typedef long int64; typedef struct int128 { int64 lo,hi; }int128; typedef struct fvec { float x,y,z,w; }fvec; typedef struct ivec { int32 x,y,z,w; }ivec; typedef struct ivec16 { int16 x,y,z,w; }ivec16; typedef union vec { int128 value; fvec vf; ivec mi; int16 vi; }vec; |
//============================ // clases de Registro genérico VF y VI //============================ class VUReg { private: int vstall; int lRead; int lWrite; protected: vec data; public: int stall(); //stall states before can accessed int lastRead(); //last instruction in read value int lastWrite(); //last instruction in write value void stall(int v); //set previously defined values void decstall(); //decrease stall, this occurs when a cycli is completed void lastRead(int v); //set las instrcución read void lastWrite(int v); //set last instruction write }; class VFReg:public VUReg { public: float x(); //GET values float y(); float z(); float w(); void set(float v); //SET values void x(float v); void y(float v); void z(float v); void w(float v); void xtoi(float v, int a); //Used to cast between types void ytoi(float v, int a); void ztoi(float v, int a); void wtoi(float v, int a); void xtof(int v, int a); void ytof(int v, int a); void ztof(int v, int a); void wtof(int v, int a); void mcopy(int *dest, int reg); //Used to get memory data void mwrite(int *org, int reg); //Used to set memory data }; class VIReg:public VUReg { public: int16 value(); //read values from register void value(int16 v); //sets values on register }; |
//============================ // clases usadas por el PARSER //============================ //TYPES of flavors //0 - normal //1 - i register //2-5 - bc broadcast 2-x 3-y 4-z 5-w //6 - q register //7 - a acumulator //8 - ai acumulator and i register //9 - aq acumulator and q register //10-13 - abc acumulator broadcast 10-x 11-y 12-z 13-w //======================================= // Types of params // VI, VF, VIDEST, VFDEST, ACC, IMM24, IMM15, IMM12, IMM11, IMM5, // I, IMM11(VI), IMM11(VI)DEST, (VI)DEST, (--VI), (VI++), // P, Q, R, VI01
|
//================================= // definición de instrucción, símbolo y parámetro //================================= //======================================= // Types of params // VI, VF, VIDEST, VFDEST, ACC, IMM24, IMM15, IMM12, IMM11, IMM5, // I, IMM11(VI), IMM11(VI)DEST, (VI)DEST, (--VI), (VI++), // P, Q, R, VI01 class VUParam { public: void Reset(); //used when reset int type; //kind of parameter vf, vi, value, other int index; //if vf-vi-other index i.e. VI3 VF8 VF31 char sufix[5]; //x-y-z-w-combination unsigned long udata; //data value long data; //data value char label[50]; //if parameter is a symbol, this indicates the label int stalling; //used for calculate stallings int memdir; //address of instruction }; class VUInstruction { public: void Reset(); //used when reset char flg; //has the instruction a [E] flag or similar? int addr; //memory address int tics; //tics counter int breakpoint; //user breackpoint? int SymbolIndex; //Symbol index, only for easy drawing int InstIndex[2]; //pointer to instruction class int flavor[2]; //flavor of instruction char dest[2][50]; //XYZW and combinations VUParam Params[2][4]; //parámeters }; class Symbol { public: char symb[50]; //if line is a symbol this containts the address of label int Line; }; |
class VU { public: VFReg RegFloat[32]; //32 float registers VIReg RegInt [16]; //16 integer registers VFReg ACC, I, Q, P, R; //special registers //P & Q are only 1 float so I use only the x value uint16 PC; //program counter int64 clock; //clock ticks ivec dataMem[1000]; //data memory 16 Kb VUInstruction program[1000]; //program to be executed int NInstructions; //number of instructions to be executed Symbol Labels[500]; //as much as 500 labels int NSymbols; Symbol MemDir[500]; //as much as 500 labels int NMemDir; //FLAGS uint16 ClipFlag[4]; int StatusFlag; char MacZ,MacS,MacU,MacO; VU(); void Tic(void); //do all the tastk when running int DoUpper(); //perform upper part of instruction int DoLower(); //perform lower part of instruction void Reset(); //used when reset void DecStall(); //decrease stall on all registers int Stalling(VUParam &a); //calculate if a rgister is stalling void (*CallBack) (int mode, int error); //callback function to draw all properly void MemVal2(uint16 v2,int16 *v3); //get/set memory values in diferents formats void MemVal16(uint16 v2,char pos, int16 *v3); void MemSetVal16(uint16 v2,char pos, int16 v3); //all the rest are instructions definitios.
//UPPER instructions |
Hay mucho por hacer. Lo he dividido en varios apartados y me iré metiendo con ello cuando me apetezca. De momento estoy saturado de aplicación.
PARSER
El módulo del parser es el que se encarga de leer los ficheros de código y
memoria y cargarlos en el emulador de VU1. Se pueden mejorar muchos aspectos
como:
FRONT
El front no es parte de la emulación en sí, tan solo hace accesible la clase al
usuario. Aunque el front que hay es sencillo se podrían añadir más opciones y
escribir fronts para otras plataformas. De momento se me ocurren las siguientes
mejoras:
EMULADOR
El emulador son las clases que simulan ser una VU1. Hay muchas mejoras que hacer
y cosas a revisar. De entrada: