Raster estable – Técnica de la doble interrupción

En el tutorial de hoy veremos como hacer un raster estable que, si bien no es complicado, requiere tener un poco mas de conocimiento del VIC2 y de la duración de las instrucciones del 6502. Voy a poner 4 ejemplos de código, el primero de los cuales es una interrupción simple (en la que cambiamos el color como lo hicimos en anteriores posts), en el segundo ejemplo muestro ya una primera aproximación a la técnica, en la cual pongo con color blanco la parte sin estabilizar, y en color rojo la parte estabilizada.
En el tercer ejemplo muestro la corrección final, en la que se ve que el cáracter que parpadeaba ya no parpadea más, y finalmente en el cuarto ejemplo esta el código completo y comentado para que se entienda, con las explicaciones y la cantidad de clocks utilizados por cada instrucción.

Técnicas para estabilizar/sincronizar el raster hay varias, yo me decidí por esta debido a que es bastante común y en teoría simple. Digo en teoría porque estuve un par de semanas para entender los detalles a fondo, pero al final pude comprender bastante como funciona.

Cuando intentamos hacer un rasterbar nos encontramos con algo bastante molesto: por más que nosotros seteemos una interrupción y hagamos todos los deberes en el medio de la pantalla nos queda una vibración (a la que llamaremos ‘jitter’ a partir de ahora) bastante molesta, como la que pueden ver a continuación:

Este es el código correspondiente

BasicUpstart2(main)

.const RASTERUNSTABLE = $1
.const RASTERCOLOR1 = $2
.const NORMALCOLOR = 0

.const LINE = 134 	
.const LINE2 = 160

main:
{
	sei                           
	lda #$7f		
	sta $dc0d
	sta $dd0d
	lda #$35
	sta $01	
	lda $d01a		
	ora #$01
	sta $d01a

	lda $d011		
	and #$7f
	sta $d011

	lda #LINE
	sta $d012 

	lda #intcode
	sta $ffff

	asl $d019	
	bit $dc0d   
	bit $dd0d	

	cli          

dummy_loop:	
	ldx #$00
	nop
	inx
	nop
	ldx $d020
	cpx #33
	nop
	ldy #$00
	iny
	bit $00
	nop 
	ldx $d020
	nop
	ldx #$00
	nop
	inx
	ldx $d020
	nop
	cpx #33
	nop
	ldy #$00
	iny
	ldx $d020
	bit $00
	nop 
	ldx $d020
	nop
	ldx #$00
	nop
	ldx $d020
	inx
	nop
	cpx #33
	nop
	ldy #$00
	ldx $d020
	iny
	bit $00
	nop 
	ldx $d020
	nop
	ldx #$00
	nop
	inx
	ldx $d020
	nop
	cpx #33
	nop
	ldy #$00
	iny
	ldx $d020
	bit $00
	nop 
	ldx $d020
	nop
	jmp dummy_loop
}

intcode:
{
	pha
	txa	
	pha
	tya 
	pha

	setColor(RASTERUNSTABLE)
	
	lda #intcode_restore
	sta $ffff

	ldx #LINE2
	stx $d012 

	asl $d019					  

	pla
	tay
	pla
	tax
	pla

	rti
}

intcode_restore: 
{
	pha
	txa	
	pha
	tya 
	pha

	setColor(NORMALCOLOR)

	lda #intcode
	sta $ffff

	ldx #LINE
	stx $d012 

	asl $d019					  

	pla
	tay
	pla
	tax
	pla

	rti
}


.macro setColor(color) {
	lda #color
	sta $d020
	sta $d021
}

¿Y esto por que esta ocurriendo? ¿No se supone que el VIC avisa al procesador cuando llegamos a esa linea?

Pues si… y no. El VIC2 avisa al procesador mediante una interrupción que el raster llegó a dicha linea… y sigue su tarea, que es seguir dibujando la pantalla (y allí es donde se va todo a la m…). En el camino el procesador recibió la interrupción, finalizó la instrucción que estaba ejecutando, guardo el estado en el stack, hizo el salto a nuestra rutina… para este punto el raster ya esta por la mitad de la pantalla, y encima, como la instrucción que estaba ejecutando puede tener una longitud entre 2 y 7 ciclos de clock, nos aparece ese condenado jitter.
El problema es que no podemos saber de antemano que instrucción estaba ejecutando el procesador. Si tuviesemos una forma de saber que se estaba ejecutando o MEJOR AUN podemos especificar una instrucción de antemano, entonces el jitter desaparece (la mayoría, después veremos que queda un chiquito por eliminar).
A alguien muy creativo se le ocurrió lo siguiente:

“Si pongo un punto de interrupción en una linea, inmediatamente cuando sucede la interrupción SETEO LA SIGUIENTE LINEA, y luego completo el resto de los ciclos de clocks que faltan para terminar la misma con una instrucción conocida (8 NOP seguidos), entonces cuando ocurra la próxima interrupción SABRE QUE SE ESTA EJECUTANDO UN NOP”

Efectivamente, como podemos ver en la siguiente imagen:

BasicUpstart2(main)

.const RASTERUNSTABLE = $1
.const RASTERCOLOR1 = $2
.const NORMALCOLOR = 0

.const LINE = 132 	
.const LINE2 = 160

main:
{
	sei                           

	lda #$7f		
	sta $dc0d
	sta $dd0d

	lda #$35
	sta $01	

	lda $d01a		
	ora #$01
	sta $d01a

	lda $d011		
	and #$7f
	sta $d011

	lda #LINE
	sta $d012 

	lda #intcode
	sta $ffff

	asl $d019	
	bit $dc0d   
	bit $dd0d	

	cli          

dummy_loop:	
	ldx #$00
	nop
	inx
	nop
	ldx $d020
	cpx #33
	nop
	ldy #$00
	iny
	bit $00
	nop 
	ldx $d020
	nop
	ldx #$00
	nop
	inx
	ldx $d020
	nop
	cpx #33
	nop
	ldy #$00
	iny
	ldx $d020
	bit $00
	nop 
	ldx $d020
	nop
	ldx #$00
	nop
	ldx $d020
	inx
	nop
	cpx #33
	nop
	ldy #$00
	ldx $d020
	iny
	bit $00
	nop 
	ldx $d020
	nop
	ldx #$00
	nop
	inx
	ldx $d020
	nop
	cpx #33
	nop
	ldy #$00
	iny
	ldx $d020
	bit $00
	nop 
	ldx $d020
	nop
	jmp dummy_loop
}

intcode:
{
	pha
	tya	
	pha
	txa 
	pha


	lda #intcode_stable
	sta $ffff

	setColor(RASTERUNSTABLE)

	inc $d012 
	asl $d019					  

    tsx
    cli

    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
}


intcode_stable:
{
	txs
	waitLine(8)

	noEstabilizaJitter()

	setColor(0)
	nop
	nop
	nop
	
	setColor(RASTERCOLOR1) 

	lda #intcode_restore
	sta $ffff

	ldx #LINE2
	stx $d012 

	asl $d019					  

	pla                           
	tax                           
	pla                           
	tay                           
	pla                           

	rti
}

intcode_restore: 
{
	pha
	tya	
	pha
	txa 
	pha

	setColor(NORMALCOLOR)

	lda #intcode
	sta $ffff

	ldx #LINE
	stx $d012 

	asl $d019					  

	pla                           
	tax                           
	pla                           
	tay                           
	pla                           

	rti
}


.macro waitLine(cant_loops) {
    ldx #cant_loops	
    dex      		
    bne *-1  		
    bit $00  		
}


.macro estabilizaJitter() {
	lda $d012 
	cmp $d012 
	beq *+2   
}


.macro noEstabilizaJitter() {
	nop 
	nop 
	nop
	bit $00 
	nop 
}

.macro setColor(color) {
	lda #color
	sta $d020
	sta $d021
}

Peeero… está CASI estable. Si bien el jitter disminuye considerablemente, todavía queda una linea de un cáracter que esta parpadeando. Esto ocurre porque, si bien nuestra interrupción ocurrió durante una instrucción NOP, esta puede haber estado ejecutándose o haber finalizado, lo cual nos da esa diferencia de un clock.

Para ello tenemos que introducir una pausa (puede ser un bucle) MUY precisa, para que nos lleve CASI al final de la linea (una linea completa lleva 63 ciclos de procesador, en una c64 pal, y en cada ciclo el VIC2 dibuja una linea de un cáracter de ancho). Allí cargamos en A el valor de $d012, inmediatamente lo comparamos con el valor de $d012 (que loco no?) y si es igual saltamos… a la siguiente linea de código.
Esto que estoy explicando parece un sinsentido total, es mas, les muestro el código para que vean:

lda $d012 // 4 clocks
cmp $d012 // 5 clocks
beq siguiente // 2 clocks si distinto o 3 clocks si igual
// si es igual va a siguiente y si no tambien… pfff
siguiente:
// el resto
// del codigo
// aqui…

O sea… si es distinto va a siguiente y si es igual tambien… CUAL ES LA LOGICA?
A lo mejor ustedes ya lo entendieron, esta fue la parte que mas me costó comprender. En primer lugar, este bloque de código tiene sentido cuando el raster ESTA CERCA DE FINALIZAR LA LINEA. El truco pasa por comparar el valor de $D012 (la linea de raster) con el valor que toma unos pocos clocks después. Si es el mismo valor quiere decir que la interrupción ocurrió justo cuando finalizó la ejecución del NOP, entonces SALTA a la siguiente instrucción, lo cual le insume 3 ciclos de clock.
Ahora bien, si el valor da diferente es porque hubo un clock de más (producido porque se estaba ejecutando todavía el NOP). El salto no se ejecuta,, lo cual le insume 2 ciclos de clock… a la siguiente instrucción (a la misma que hubiera hecho el salto) ¿Ven la magia? si hay un clock de mas suma 2 clocks, si no hay clock suma 3… y se nos estabiliza el raster.

BasicUpstart2(main)

.const RASTERUNSTABLE = $1
.const RASTERCOLOR1 = $2
.const NORMALCOLOR = 0

.const LINE = 52
.const LINE2 = 132

main:
{
	sei                           

	lda #$7f		
	sta $dc0d
	sta $dd0d

	lda #$35
	sta $01	

	lda $d01a
	ora #$01
	sta $d01a

	lda $d011
	and #$7f
	sta $d011

	lda #LINE
	sta $d012 

	lda #intcode
	sta $ffff

	asl $d019
	bit $dc0d
	bit $dd0d

	cli          

dummy_loop:	
	ldx #$00
	nop
	inx
	nop
	ldx $d020
	cpx #33
	nop
	ldy #$00
	iny
	bit $00
	nop 
	ldx $d020
	nop
	ldx #$00
	nop
	inx
	ldx $d020
	nop
	cpx #33
	nop
	ldy #$00
	iny
	ldx $d020
	bit $00
	nop 
	ldx $d020
	nop
	ldx #$00
	nop
	ldx $d020
	inx
	nop
	cpx #33
	nop
	ldy #$00
	ldx $d020
	iny
	bit $00
	nop 
	ldx $d020
	nop
	ldx #$00
	nop
	inx
	ldx $d020
	nop
	cpx #33
	nop
	ldy #$00
	iny
	ldx $d020
	bit $00
	nop 
	ldx $d020
	nop
	jmp dummy_loop
}

intcode:
{
	pha
	tya	
	pha
	txa 
	pha


	lda #intcode_stable
	sta $ffff

	setColor(RASTERUNSTABLE)

	inc $d012 
	asl $d019					  

    tsx
    cli

    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
}


intcode_stable:
{
	txs

	waitLine(8)

	estabilizaJitter()

	setColor(0)
	nop
	nop
	nop

	setColor(RASTERCOLOR1) 

	lda #intcode_restore
	sta $ffff

	ldx #LINE2
	stx $d012 

	asl $d019					  

	pla                           
	tax                           
	pla                           
	tay                           
	pla                           

	rti
}

intcode_restore: 
{
	pha
	tya	
	pha
	txa 
	pha

	setColor(NORMALCOLOR)
	dec $d020

	lda #intcode
	sta $ffff

	ldx #LINE
	stx $d012 

	asl $d019					  

	pla                           
	tax                           
	pla                           
	tay                           
	pla                           

	rti
}


.macro waitLine(cant_loops) {
    ldx #cant_loops	
    dex      		
    bne *-1  		
    bit $00  		
}


.macro estabilizaJitter() {
	lda $d012 
	cmp $d012 
	beq *+2   
}


.macro noEstabilizaJitter() {
	nop 
	nop 
	nop
	bit $00 
	nop 
}

.macro setColor(color) {
	lda #color
	sta $d020
	sta $d021
}

En estos ejemplos previos puse una linea blanca, que indica el punto de la primera interrupción y la parte sin estabilizar, luego en negro para mostrar el resto de jitter, y finalmente en rojo la parte estabilizada. Pueden ver que la linea final de la rasterbar sigue con jitter, esto es porque no me tomé el trabajo de estabilizarla.
El código final está a continuación, en el mismo estan cada uno de los pasos a realizar comentados y ademas los ciclos de clock que lleva cada instrucción o macro utilizada.

BasicUpstart2(main)

.const RASTERUNSTABLE = $1
.const RASTERCOLOR1 = $2
.const NORMALCOLOR = 0

// no podemos poner LINE = 131, 139, 147, 155... porque son badlines y rompen
// hay que recalcular waitLine() para las bad lines
.const LINE =  132 	
.const LINE2 = 160

main:
{
	sei                           
					
	// disable CIA
	lda #$7f		
	sta $dc0d
	sta $dd0d

	// Bank out kernal and basic
	// ponemos disponibles 
	// $A000-$BFFF (BASIC) 
	// y $E000-$FFFF (KERNAL)
	lda #$35
	sta $01	
/*
	Basicamente: apagamos las roms del basic y el kernel 
	(realmente, nos interesa apagar el kernel porque 
		necesitamos escribir un valor alli en $fffe y $ffff 
		y el sistema pueda leer lo que escribimos) 
	mas info leer: https://dustlayer.com/c64-architecture/2013/4/13/ram-under-rom
*/
	// aca: todo lo de antes... 
	lda $d01a		// enable VIC IRQ
	ora #$01
	sta $d01a

	lda $d011		// clear MSB raster
	and #$7f
	sta $d011

	lda #LINE
	sta $d012 

	lda #intcode
	sta $ffff

	// esto lo hacemos para que no haga 
	// cosas raras cuando arranca... 
	asl $d019	// Ack any previous raster interrupt
	bit $dc0d   // reading the interrupt control registers 
	bit $dd0d	// clears them

	cli          


// este dummy loop es para que la interrupcion ocurra 
// en cualquier instruccion, asi tenemos un caso lo mas 
// real posible... en los ejemplos que encontre 
// normalmente hacian un jmp *, lo cual no era real, ya que 
// la interrupcion ocurria en una instruccion que conocemos (JMP)
dummy_loop:	
	ldx #$00
	nop
	inx
	nop
	ldx $d020
	cpx #33
	nop
	ldy #$00
	iny
	bit $00
	nop 
	ldx $d020
	nop
	ldx #$00
	nop
	inx
	ldx $d020
	nop
	cpx #33
	nop
	ldy #$00
	iny
	ldx $d020
	bit $00
	nop 
	ldx $d020
	nop
	ldx #$00
	nop
	ldx $d020
	inx
	nop
	cpx #33
	nop
	ldy #$00
	ldx $d020
	iny
	bit $00
	nop 
	ldx $d020
	nop
	ldx #$00
	nop
	inx
	ldx $d020
	nop
	cpx #33
	nop
	ldy #$00
	iny
	ldx $d020
	bit $00
	nop 
	ldx $d020
	nop
	jmp dummy_loop
}

intcode:
{
	// GUARDAMOS REGISTROS *1
	/* 
	   En los ejemplos que vi lo hace mejor
	   escribiendo codigo automodificable
	   el cual ocupa menos clocks del procesador
	   pero a fines didacticos esto es mas claro
	*/
	pha
	tya	
	pha
	txa 
	pha


	lda #intcode_stable
	sta $ffff

	// pongo la interrupcion en la proxima linea
	inc $d012 
	asl $d019					  

    // Almacena el actual puntero del stack 
    // porque no queremos volver aca cuando se produzca el RTI
    // sino que queremos que vaya al dummy_loop 
    // (o la parte de nuestro codigo)
    tsx
    cli

    // en algun punto en los siguientes nop's 
    // se ejecutara la siguiente interrupcion
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
    nop
}


intcode_stable:
{
// en este punto, se ejecuto la interrupcion en un comando NOP,
// por lo tanto, ya tenemos 1 o 2 clocks corridos a lo sumo
// (dependiendo si estaba ejecutando la instruccion cuando llamó 
//  la interrupcion o si la había finalizado)

	// nop 1 o 2 clocks
	// salto interrupcion 7 clocks
	// 8 o 9 clocks
	
	// Restaura el puntero del stack al punto de retorno 
	// donde se llamo por primera vez (y que guardamos en X)
	// nos interesa que esté en el punto donde guardamos (*1)
	// si no el RTI va a volver acá, y no es la idea
	txs
	// 10 u 11 clocks

	// espero que pase CASI toda la linea
	// el calculo: 
	// 	2 + (7 * (2 + 3)) + 2  + 3 = 42
	waitLine(8) // (43 + (10 u 11)) = 52/53)

	// corrige el jitter de 1 clock del nop
	// para q esto funcione tenemos q estar casi al final
	// de la linea: cargamos en A el valor de $d012
	// luego lo comparamos
	// si es igual hay un ciclo de menos, entonces salta (3 clocks)
	// si es distinto pasa, hay un ciclo de mas, no salta (2 clocks)
	// y con eso se estabiliza 

	estabilizaJitter()

	setColor(RASTERCOLOR1) 

	// restauro interrupcion a 2da linea
	lda #intcode_restore
	sta $ffff

	ldx #LINE2
	stx $d012 

	asl $d019					  

	pla                           
	tax                           
	pla                           
	tay                           
	pla                           

	rti
}

intcode_restore: 
{
	pha
	tya	
	pha
	txa 
	pha
	// en este punto esta sin estabilizar
	// necesitamos hacer nuevamente todo lo que hicimos
	// anteriormente... 
	setColor(NORMALCOLOR)

	// restauro interrupcion
	lda #intcode
	sta $ffff

	ldx #LINE
	stx $d012 

	asl $d019					  

	pla                           
	tax                           
	pla                           
	tay                           
	pla                           

	rti
}


.macro waitLine(cant_loops) {
    ldx #cant_loops	//	2  
    dex      		//  2
    bne *-1  		//	3
    bit $00  		//  3
}


.macro estabilizaJitter() {
	lda $d012 // 4 		(56/57)
	cmp $d012 // 5 		(62/63)
	beq *+2   // 2 distinto o 3 igual
}


.macro noEstabilizaJitter() {
	nop 
	nop 
	nop
	bit $00 
	nop 
	// 11 clocks
}

.macro setColor(color) {
	lda #color	//	2
	sta $d020	//	4
	sta $d021   //	4
}

Para el final me he dejado los detalles tediosos del VIC.

Este código funciona en tanto y cuando no usemos las lineas 51, 59, 67 … y asi de 8 en 8, las cuales se conocen como “bad lines”. ¿Que esta pasando? Bueno, en dichas lineas (que coinciden con la primera linea de una fila de caracteres) el VIC2 interrumpe durante 40 CICLOS al procesador para obtener los códigos de caracteres de una linea desde la matriz de vídeo, haciendo que se nos de-sincronice el raster y se vaya todo al demonio. Lamentablemente no encontré todavía información para evitar esto, en cuanto encuentre algo actualizo el artículo.

Por último, dejo una lista de enlaces para profundizar mas en el tema:

Double IRQ explained: http://codebase64.org/doku.php?id=base:double_irq_explained

STABLE RASTER ROUTINE: http://codebase64.org/doku.php?id=base:stable_raster_routine

VIC-II FOR BEGINNERS PART 3 – BEYOND THE SCREEN: RASTERS AND CYCLES https://dustlayer.com/vic-ii/2013/4/25/vic-ii-for-beginners-beyond-the-screen-rasters-cycle

The MOS 6567/6569 video controller (VIC-II) http://www.zimmers.net/cbmpics/cbm/c64/vic-ii.txt

Y esto es todo por hoy. Pueden descargar el código desde Github, visitar mi página en Facebook y comentar cualquier duda que tengan.
Hasta la próxima!

Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

w

Conectando a %s