Karoshi MSX Community
27 de Mayo de 2017, 03:00:52 am *
Bienvenido(a), Visitante. Por favor, ingresa o regístrate.

Ingresar con nombre de usuario, contraseña y duración de la sesión
Noticias:
 
   Inicio   Ayuda Buscar Calendario Ingresar Registrarse  
Páginas: [1]
  Imprimir  
Autor Tema: TUTORIAL: Desarrollo compatible para todas las familias de MSX (II)  (Leído 11276 veces)
0 Usuarios y 1 Visitante están viendo este tema.
jltursan
Karoshi Forum's Guru
*******
Mensajes: 1516


¿Que es lo que has aprendido hoy?


Ver Perfil WWW Email
« : 20 de Febrero de 2007, 09:05:39 pm »

PARTE II


50/60 Hz

Se pueden encontrar MSX en los cinco continentes, eso implica que el código que uno haga, debe (o debería) de tener en cuenta los diferentes velocidades de sincronismo bajo las cuales van a funcionar las máquinas. Las combinaciones que podemos encontrar, incluyendo el estandar de TV son las siguientes:

  • Japón: NTSC (60Hz)
  • Reino Unido: PAL (50Hz)
  • EEUU: NTSC (60Hz)
  • Francia: SECAM (50Hz)
  • Alemania: PAL (50Hz)
  • Rusia: NTSC (60Hz)
  • España: PAL (50Hz)

Como internacional se toma la combinación PAL/50Hz.

Si nos encontramos en un equipo MSX2 o superior podremos averiguar la velocidad del refresco leyendo el bit 1 del registro 9 del VDP, si contiene 1 estamos en una máquina a 50Hz, si vale 0 estamos en una funcionando a 60Hz (obtenemos el mismo resultado consultando el bit 7 de la posición $2B). De igual forma podremos cambiar la velocidad de refresco modificando el contenido de dicho bit en el registro. La opción a seguir, que el programa se adapte al refresco o bien que nosotros modifiquemos el refresco para adaptarlo al funcionamiento de nuestro programa......; bien, eso ya es algo a decidir por parte del programador y un tema bastante complejo, más adelante comentaré las implicaciones que puede tener el refresco al trabajar sobre la pantalla.


La BIOS

Poco y mucho que decir Wink, la existencia de la BIOS viene determinada por la necesidad de que exista un interfaz que abstraiga las diferencias en el hardware de manera que un desarrollador no necesite adaptar su código a dichas diferencias. Si queremos estar seguros que nuestro código vaya a funcionar en el 100% de las máquinas deberá utilizar siempre la BIOS, de no hacerlo así tendremos que estar muy seguros de lo que hacemos y testearlo en el mayor número de máquinas posible....y aún así nunca podremos estar seguros Tongue.


El R800 y el T9769C

El R800 fue el microprocesador diseñado por ASCII para que los equipos TurboR de Panasonic pudieran mantener la compatibilidad con el Z80 oficial impuesto por la norma y disfrutar a la vez de todas las prestaciones de un micro de "casi" 16 bits. La velocidad de la CPU es de 7,16Mhz; pero debido a que la mayoría de las instrucciones se ejecutan en sólo 1 ciclo, a diferencia de los 4 ciclos de media en un Z80 estandar, el resultado práctico es el de tener un Z80 a 28,64Mhz.
Esto da como resultado que si ejecutamos un programa concebido para que se ejecute a los "humildes" 3,57Mhz del Z80 que incorporan el resto de los modelos de MSX, vamos a encontrarnos con que  todo va a ir 4 veces más rápido si no hemos tenido la precaución de sincronizarlo con el barrido de pantalla.
Del programador depende decidir si aprovecha esto a su favor o no.
Si el programa definitivamente debe de ejecutarse a su velocidad original, no quedará otro remedio que desactivar el R800 mediante una simple llamada a la BIOS de los TurboR:

Código:
CHGCPU:  equ     $0180

ld a,$80
call CHGCPU

Un posible ejemplo de rutina de detección en la que también se maneja información acerca de la familia y de la frecuencia de refresco, podría quedar como sigue (gracias a Dioniso por el código):

Código:
CHGCPU:  equ     $0180
GETCPU:  equ     $0183
VDP_09:  equ     $FFE8

;MSX Version
ld a,($2d)
or a
jr z,MSX1
cp 3 ;turbo-r
jr nz,guess_hz
call GETCPU
or a
jr z,guess_hz
ld a,$80
call CHGCPU ;modo z80 en turbo-r
;detectamos herzios
guess_hz:
ld a,1
ld (MODELO),a ;MSX 2 o superior - podemos modificar los herzios.
ld a,(VDP_09) ; leemos el contenido del registro 9 del VDP
bit 1,a
jr z,its_60hz
xor a
ld (HERZIOS),a ; 0=50hz (variable para herzios)
jr end_check_version
its_60hz:
ld a,1
ld (HERZIOS),a ;1=60hz
jr end_check_version

MSX1:
xor a
ld (MODELO),a

end_check_version:

Con ello obtendriamos en la variable MODELO, el modelo de MSX (0=MSX1, 1=MSX2 ó superior) y en la variable HERZIOS, la velocidad de refresco (0=50Hz, 1=60Hz)

Si no queremos modificar permanentemente la velocidad podemos optar por mantenerla parcialmente; como ejemplo podríamos tomar una rutina de cálculo compleja, en este caso activariamos el R800 al no acceder a la pantalla y así aprovechar esa velocidad extra de proceso.

Otro de los problemas relacionados con el uso del R800 es que no es del todo 100% compatible con las características no oficiales del Z80, más concretamente los bits 3 y 5 del registro F (que normalmente contienen la copia o variantes de los respectivos bits 3 y 5 del registro A) no tienen el mismo comportamiento. Igualmente mnemónicos no oficiales como SLL han desaparecido, éste concretamente han sido reemplazado por TST, que tampoco es oficial. Tongue

La conclusión es que lo mejor que se puede hacer es limitarse a usar en lo posible las operaciones documentadas por Zilog y dejar de lado las no oficiales (aunque en algunos casos sea tentador usar un LD IXL,a Wink).

Similar al problema de velocidad que plantea el R800, es el caso del T9769C de Toshiba que incorporan los modelos de Panasonic MSX2+, este micro puede funcionar a 2 velocidades, 3.57MHz y 5.37MHz. Es evidente que aunque nos parezca atractiva la posibilidad de activar el modo "turbo", no debemos desarrollar nuestro software específicamente para que corra a esta velocidad; sería muy lento para el resto de los mortales... Tongue


Tengo sobrefrecuenciado mi Pentium 4, ¿como puedo hacer lo mismo con mi MSX??

Como ya he dicho, esto se podrá hacer únicamente sobre los modelos de Panasonic FS-A1FX/WX/WSX. A través del acceso a un par de puertos exclusivos de estos modelos, podremos conmutar la velocidad de la CPU:

($40)   = 8 (ID Panasonic)
($41) bit 0: Velocidad CPU (0=5.37MHz, 1=3.57MHz)

Ejemplo (obtenido del MAP) de como se activaría el modo turbo:


Código:
ld a,8
out ($40),a ; se envía el código de fabricante 8 (Panasonic) al puerto $40
in a,($40) ; se lee el valor que acabamos de enviar
cpl ; complemento de todos los bits de ese valor
cp 8 ; si no volvemos a obtener el valor originalmente enviado (8),
jr nz,Not_WX ; no se trata de un WX/WSX/FX, no hay modo turbo :(.
xor a ; si lo es, escribimos 0 en el puerto $41
out ($41),a ; y activamos el modo de alta velocidad



Puertos de acceso al VDP

Aunque en el 99,99% de los casos se trata de los puertos $98 y $99, la norma determina que pueden ser otros y su valor estará definido en VDP.DR ($0006) y VDP.DW ($0007). Según el TH esta sería la tabla de equivalencias de los puertos de video en el MSX :

Para todos los MSX:

Código:

----------------------------------------------------------------------
|      Port   | Address |     Function      |
----------------------------------------------------------------------
| port #0 (READ)  |    n    | read data from VRAM      |
| port #0 (WRITE) |    n'   | write data to VRAM                     |
| port #1 (READ)  |  n + 1  | read status register      |
| port #1 (WRITE) |  n'+ 1  | write to control register              |
----------------------------------------------------------------------


Exclusivamente para modelos superiores a un MSX1:

Código:

----------------------------------------------------------------------
|      Port   | Address |     Function      |
----------------------------------------------------------------------
| port #2 (WRITE) |  n'+ 2  | write to palette register              |
| port #3 (WRITE) |  n'+ 3  | write to indirectly specified register |
----------------------------------------------------------------------


Siendo n el valor obtenido a partir de VDP.DR y n' el obtenido de VDP.DW.

Si se quiere hacer un juego 100% compatible, nuestro código deberá leer el contenido de estas variables y utilizar los valores obtenidos como puertos de acceso al VDP en el resto de programa. El ejemplo aplicado a un SETWRT:

Código:
ld a,[7]
ld c,a
inc c
ld a,l
di
out (c),a
ld a,h
and $3F
or $40
ei
out (c),a
   

Si no recurrimos a código automodificable, esto supone una pérdida importante de velocidad y por eso es habitual el uso directo de los puertos estandar.


Acceso contenido a la VRAM (o como acceder a la VRAM sin usar la BIOS y no morir en el intento)

Uno de los casos típicos en los que puede que decidamos obviar el uso de la BIOS (es un poco lenta) es el del acceso a la VRAM. En la mayoria de los modelos de MSX1, dicho acceso a la VRAM debe de hacerse con contención ya que la velocidad del Z80 a la hora de enviar los datos es superior a la del TMS9918/28/29 para procesarlos. En los TMS9938/58 que incorporan las siguientes generaciones de MSX, no es necesario esperar entre acceso y acceso; pero si queremos que nuestro programa funcione, además de hacerlo en modelos MSX2 o superiores, en cualquier MSX1,  tendremos que respetar este método.
Este retardo entre acceso y acceso (OUT) al VDP, en términos de ciclos del Z80, deberá ser equivalente a 28 ciclos.


¿Y por qué hay que esperar 28 ciclos?

Según el datasheet oficial del TMS9918, el retardo base en los accesos al VDP es de 2 microsegundos, por lo que en principio habrá que calcular cuantos ciclos de espera supondrán para un Z80 a 3,579545Mhz.

Si tenemos que:
1T/3579545 = 0,000000279 segundos/ciclo

Entonces ya podemos saber a cuantos ciclos equivalen esos 2 microsegundos:
0,000002/0,000000279 = 7,16845 ciclos

Así pues, suponen 7,16 ciclos del Z80 de los MSX; hay que esperar entre 7-8 ciclos de media. Además de esto en función del modo de pantalla habrá que esperar (o no) un poco más antes de poder acceder al dato en la VRAM (tanto da leer como escribir). Para screen 2 tenemos un retardo extra que va de 0 a 6 microsegundos, por lo que si convertimos a ciclos, en el peor de los casos tendremos que esperar hasta 28T-29T. La excepción la tenemos únicamente durante una ventana que dura aproximadamente 4300 microsegundos (a 60Hz) tras el retrazado vertical, en ese intervalo el retardo extra es cero. Durante ese intervalo basta con esperar esos 7-8 ciclos iniciales.




Asumiendo esta limitación y si tomamos como ejemplo el OUT más práctico (que no el más rápido), el envio de datos sucesivos seguiría este patrón:

Código:
outi
nop
nop
outi
nop
nop
...
...
...

Como el tiempo empleado en el OUTI hay que tenerlo en cuenta (18 ciclos), nos bastaría con añadir otros 10 ciclos más (2 NOP de 5 ciclos cada uno) para garantizar el correcto funcionamiento en cualquier modelo de MSX1 y en cualquier momento del refresco de pantalla. Se puede ajustar algo más y reducir los NOPs a sólo uno; pero bueno, si queremos estar 100% seguros tendremos que hacerlo así.

Antes he comentado que existe una ventana de 4300 microsegundos tras el retrazado vertical cuando el refresco es de 60Hz, en ese intervalo los datos pueden ser enviados sin retardos extras y a la máxima velocidad posible. ¿Cuantos bytes podriamos transferir teóricamente durante ese intervalo?. Para calcularlo primero averiguamos cuantos ciclos tenemos disponibles durante ese lapso de tiempo :

(4,3ms * 3579545) / 1000ms = 15392 ciclos

Sabemos que un OUTI consume 18 ciclos:
15392 / 18 (ciclos OUTI) = 855 instrucciones OUTI

Así pues, podriamos encadenar 855 OUTIs uno detrás de otro sin esperas, lo que da poco más de 1/6 de pantalla o 3 lineas y pico de caracteres. A partir de ahí ya no tenemos ninguna garantía de que no se produzca corrupción del flujo de datos, tendremos que empezar a intercalar retardos. Este resultado es aproximado, en el siguiente apartado se tratará de calcular con más precisión este valor.
Por cierto, en este intervalo podriamos usar un OTIR (marginado él); pero claro ya nos podriamos ir olvidando de esos 855 bytes; eso sí, a costa de la pérdida de velocidad ganaríamos bastante memoria.


Diferencias en el acceso al VDP en equipos con refresco 50Hz/60Hz

Siguiendo con el punto anterior, si estamos aprovechando el retrazado vertical para volcar datos a granel tendremos que tener en cuenta de la frecuencia a la que estemos trabajando, 50Hz ó 60Hz. Estas diferencias están determinadas por el tratamiento específico que tiene el VDP de la pantalla según corresponda:

Para los equipos funcionando a 60Hz (realmente son 59,94Hz), tenemos que la pantalla está diferenciada en estas zonas:

 Linea  Descripcion

 192    Pantalla activa
 24,5   Borde inferior
 3      Intervalo inferior
 3      Intervalo vertical
 13     Intervalo superior
 27     Borde superior
 ------
 262,5  lineas en total
 
Si dejamos de lado las 192 que conforman la pantalla activa, nos quedan 70,5 lineas durante las cuales podremos trabajar sin retardos.
 
Ahora calculamos cuantos ciclos de CPU corresponden a cada linea:

1 pantalla / 60 = 0,016683350016683350016683350016683 seg.
1 linea = 0,016683350016683350016683350016683 seg / 262,5 = 0,0000635556 seg.


La duración total del retrazado será:

0,0000635556 * 70.5 = 0,0044806711473378140044806711473378 seg. de VBlank (4480 microsegundos, más o menos los 4300 del datasheet Tongue)

Como previamente ya habiamos calculado a cuantos segundos equivalía 1T, tenemos que:

0,0000635556 / 0,000000279 = 227,79T por linea

y finalmente:

227,79T * 70.5 = 16059,748T por VBlank

Resumiendo:

1 linea = 0,0000635556 seg. = 227,8T
1 VBlank = 70,5 lineas = 16059,75T

                                                                                       
Ya podemos calcular el número de OUTIs:

16059,75T / 18 = 892 OUTIs

Esta es la peor de las situaciones ya que es la que deja menos tiempo libre al Z80. Lo recomendable pues, es desarrollar teniendo en mente la compatibilidad y restricciones de velocidad de un refresco a 60Hz y así hacer más fácil la posterior adaptación a PAL.

Con un refresco de 50Hz, las zonas serán las mismas; pero no así su extensión:

 Linea  Descripcion

 192    Pantalla activa
 48,5     Borde inferior
 3      Intervalo inferior
 3      Intervalo vertical
 13     Intervalo superior
 54     Borde superior
------
 313,5 lineas

En el caso del PAL, nos quedan 121,5 lineas durante las cuales podremos trabajar sin retardos.
 
Ahora calculamos cuantos ciclos de CPU corresponden a cada linea:

1 pantalla = 0,02 seg.
1 linea = 0,02 seg / 313,5 = 0,0000637958 seg.


La duración total del retrazado será:

0,0000637958 * 121,5 = 0,0077511961 seg. de VBlank (7751 microsegundos)

Ahora calculamos los T por linea:

0,0000637958 / 0,000000279 = 228,65T por linea

Deberían salir idénticas cantidades si los valores de 1/50 y 1/59,94 fueran exactos Sad); pero el baile de decimales continua...
 
y finalmente:

228,65T * 121,5 = 27782,06T por VBlank

Resumiendo nuevamente:

1 linea = 0,0000637958 seg. = 228,65T
1 VBlank = 121,5 lineas = 27782,06T

                                                                                       
Ya podemos calcular el número de OUTIs:

27782,06 / 18 = 1543 OUTIs

Está claro que la frecuencia de 50Hz nos da mucho más margen; pero es mejor no acostumbrase...Tongue


** Gracias a ARTRAG por corregir los valores del número de lineas por fotograma y por los valores más aproximados de refresco Smiley **


Bibliografía en la web:


ASCII MSX Technical Handbook

TMS9918A/TMS9928A/TMS9929A Datasheet

Sega Master System VDP documentation

Z80 Flag Affection

MSX I/O ports overview

MSX1 Memory Map

MSX2 Memory Map

« Última modificación: 21 de Febrero de 2007, 04:45:10 pm por Viejo_archivero » En línea

Doom dee doom dee doom
jltursan
Karoshi Forum's Guru
*******
Mensajes: 1516


¿Que es lo que has aprendido hoy?


Ver Perfil WWW Email
« Respuesta #1 : 20 de Febrero de 2007, 09:12:14 pm »

Aquí está la nueva extensión del tutorial, me he visto obligado a partirlo porque ya no cabía. Como siempre, si alguien caza algún fallo o ve algo con lo que no esté de acuerdo que lo diga por estos lares. Wink
En línea

Doom dee doom dee doom
MsxKun
Karoshi Forum's Guru
*******
Mensajes: 1554


Kimochi-ii


Ver Perfil WWW Email
« Respuesta #2 : 20 de Febrero de 2007, 09:35:12 pm »

Buen currazo, si señor  Cheesy
En línea

--

Cindy Lauper She Bops!
SapphiRe
Visitante
« Respuesta #3 : 21 de Febrero de 2007, 02:56:10 pm »

Yo aún diría más: Un currazo impresionante!! ¿Dónde está el smiley de quitarse el sombrero? Cheesy
En línea
Dioniso
Visitante
« Respuesta #4 : 21 de Febrero de 2007, 03:35:10 pm »

Sólo dos cosas:

1-Se podría quitar el XOR A en:

MSX1:
xor a
ld (MODELO),a

2-Faltaría leer el bit 7 de $2b para ver si se trata de un MSX1 a 60hz (japonés, ...) o a 50hz (europeo, ...)
En línea
e_sedes
Karoshi Maniac
****
Mensajes: 442



Ver Perfil Email
« Respuesta #5 : 21 de Febrero de 2007, 04:15:14 pm »

Estupendo curro, si señor.  Cheesy
En línea

sempre fun un valente corredor
Dioniso
Visitante
« Respuesta #6 : 21 de Febrero de 2007, 04:40:24 pm »

De acuerdo con todos. Vaya curro y gracias por el curro.
En línea
SapphiRe
Visitante
« Respuesta #7 : 21 de Febrero de 2007, 04:52:02 pm »

JL, te vamos a dar el premio Curro Jiménez Grin Grin Grin Grin Grin


vale, ya me callo... Grin Grin Grin Grin Grin Grin
En línea
theNestruo
Karoshi Lover
***
Mensajes: 233


Ver Perfil Email
« Respuesta #8 : 20 de Diciembre de 2012, 10:54:20 pm »

Perdón por reflotar este hilo tan antiguo. En realidad quería reflotar otro más antiguo aún (la primera parte de éste), pero estaba cerrado. En aquel hilo se hablaba de la RAM: que sólo podemos asumir que existe RAM en la tercera página, y que el mínimo de RAM "en la práctica" es 16KB, ya que sólo se conoce un modelo con 8KB.

Mi pregunta es relativa a la detección de RAM disponible. Hasta ahora partía de $E000 para mis variables, pero como necesito los 16KB, ¿es posible determinar de alguna forma si sólo hay 8KB en vez de 16KB?
Mi idea es tener controlado el fallo (mostrar un mensaje tipo "Hola, usuario de Casio PV7 :-P") en vez de dejar que el código avance y falle de cualquier manera.

Y aprocevhando que estoy aquí, ya os pregunto también por las variables de sistema.
En los listados de variables de sistema que he visto, dichas variables empiezan en $F380 y están bastante juntitas hasta llegar a CURLIN $F41C. De ahí saltan hasta $F91F ("Character set SlotID", seguida de "Character set address").
Entre medias hay algo más de 1KB que ¿se podría aprovechar? (tranquilos, prometo no hacerlo :-P).
Y en el caso de que sí se pudiera utilizar, ¿se podría pisar también todo lo que hay después de BDRCLR $F3EB (hasta CURLIN)? Tiene pinta de ser utilizado sólo por el BASIC...

Un saludo!
En línea

theNestruo."Old BASIC programmers never die; they GOSUB but never RETURN."
Mortimer
Karoshi Lover
***
Mensajes: 216


Ver Perfil WWW
« Respuesta #9 : 21 de Diciembre de 2012, 08:18:38 am »

Pues para determinar si hay 16KB disponibles, tan fácil como comprobar si la posición C000h es capaz de conservar lo que escribamos. Cómo no sabemos lo que hay a priori, lo suyo es leer la posición, modificar el valor (Complementando por ejempo), escribir, y leer de nuevo para comprobar si se ha guardado el cambio. También he mirado la lista de variables de sistema y la FC48: Bottom of equiped RAM te serviría. He hecho pruebas en el Blue con 32, 16 y 8KB y funciona correctamente, sólo tendrías que mirar si es igual o menor C000 para saber si tienes al menos 16KB.

En cuanto al hueco que comentas en el área de sistema entre F41Ch y F91Fh, sí qué parece que que es un área que sólo utiliza BASIC, así que en teoría, sí podrías utilizarlo, aunque claro, habría que hacer muchas pruebas para asegurarse de que luego es 100% compatible, quizás se podría mirar si las funciones BIOS que usan hacen referencia a esta área para asegurarse.

Saludos!
« Última modificación: 21 de Diciembre de 2012, 04:49:07 pm por Mortimer » En línea
theNestruo
Karoshi Lover
***
Mensajes: 233


Ver Perfil Email
« Respuesta #10 : 21 de Diciembre de 2012, 06:08:14 pm »

Pues para determinar si hay 16KB disponibles, tan fácil como comprobar si la posición C000h es capaz de conservar lo que escribamos.
Mi duda venía justo de ahí, que no sabía si se podía determinar directamente escribiendo/leyendo o habría algún tipo de "multiplexado" (i.e.: que escribas en la C000 y luego eso se pueda leer en la C000 y en la E000) con lo que el método de detección manual no funcionaría.

Muchas gracias!
En línea

theNestruo."Old BASIC programmers never die; they GOSUB but never RETURN."
nanochess
Karoshi Lover
***
Mensajes: 142


Programando algo buenísimo :)


Ver Perfil WWW
« Respuesta #11 : 01 de Enero de 2013, 05:55:20 pm »

La verdad es que detectar entre 8 y 16K o más sería bastante simple:

    LD HL,$C000
    LD (HL),$55
    LD HL,$E000
    LD (HL),$AA
    LD HL,$C000
    LD A,(HL)
    CP $55
    JP Z,.1         ; Salta si tiene 16K o más
    ...                 ; Sigue aquí si tiene 8K.

y esta rutinita maneja todos los casos (mapeado duplicado y tambíen inexistente) Smiley

Lo más simpático es que thenestruo tiene razón, sólo hay un modelo de MSX con 8K de RAM y quien sabe si alguien aún lo use.
En línea

Mira mis juegos MSX/Colecovision/Atari/Intellivision http://nanochess.org/retro_es.html, y sígueme en Twitter http://twitter.com/nanochess
MsxKun
Karoshi Forum's Guru
*******
Mensajes: 1554


Kimochi-ii


Ver Perfil WWW Email
« Respuesta #12 : 01 de Enero de 2013, 11:39:53 pm »

Lo más simpático es que thenestruo tiene razón, sólo hay un modelo de MSX con 8K de RAM y quien sabe si alguien aún lo use.

Me se de uno o dos.
En línea

--

Cindy Lauper She Bops!
nitrofurano
Karoshi Maniac
****
Mensajes: 259



Ver Perfil WWW
« Respuesta #13 : 15 de Junio de 2013, 03:18:41 pm »

  • Japón: NTSC (60Hz)
  • Reino Unido: PAL (50Hz)
  • EEUU: NTSC (60Hz)
  • Francia: SECAM (50Hz)
  • Alemania: PAL (50Hz)
  • Rusia: NTSC (60Hz)
  • España: PAL (50Hz)

Brasil: PAL-M (60Hz) - http://en.wikipedia.org/wiki/PAL#PAL-M_.28Brazil.29
En línea
Páginas: [1]
  Imprimir  
 
Ir a:  

Impulsado por MySQL Impulsado por PHP Powered by SMF 1.1.21 | SMF © 2013, Simple Machines XHTML 1.0 válido! CSS válido!