| Análisis del chip ACID del Amstrad CPC+/GX4000 |
| Sábado, 16 de Abril de 2011 01:11 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Los cartuchos compatibles con la consola Amstrad GX4000 y los ordenadores de la serie CPC+ de Amstrad necesitan un chip "llave" llamado ACID (Amstrad Cartridge Identification Device). Durante el último año colaboré en el análisis de su funcionamiento con la intención de anularlo y dejar la consola desprotegida. Paralelamente, No$Cash estudió el comportamiento del chip en sí y consiguió descifrar el algoritmo que ejecuta. Como resultado, ha sido posible describir el comportamiento del chip en un lenguaje HDL (Verilog) y su posterior implementación en una CPLD. El ACID es un pequeño integrado con encapsulado DIL de 16 pines. En la CPCWiki hay más información sobre él. Comenzaré este artículo desde el principio, es decir, desde mis primeros "escarceos" con el chip. La siguiente información se encuentra dispersa en unos cuantos posts que escribí en el hilo "¿Qué hace realmente el chip ACID de los cartuchos" en el foro de Amstrad de MiArroba. Los transcribo en orden temporal.
Note to english speakers: I wrote something about my findings at Usenet. Not as verbose as the spanish version, though. El 13 de Octubre del 2009 comencé con mi pequeña investigación... Como primera acción, procedí a medir las señales SIN y CCLR en relación al reloj CLK. SIN cambia en el flanco negativo del reloj. CCLR queda a nivel alto.
El ACID funciona independientemente del Z80. Si quito el procesador, el ACID sigue generando la señal SIN. Dado que no saqué mucho en claro viendo SIN y CCLR (No$Cash vio mucho más de lo que yo vi), afronté el problema desde otro punto de vista: ¿qué pasa exactamente en el Amstrad cuando no está el ACID? ¿Qué cambia? Si la influencia del ACID es algo tan trivial como inhibir alguna señal del procesador, dar resets (como el CIC de la NES) o algo por el estilo, quizás fuera más fácil anular ese efecto que tratar de clonar el comportamiento del chip. Obviamente, si quitas el ACID el cartucho no arranca, pero ¿por qué? Para responder a esta pregunta, escribí un programa que sólo usa memoria de la EPROM, no usa RAM para almacenamiento temporal, ni usa subrutinas, ni PUSH ni POP ni nada de eso. El programa inicializa el gate array a modo 2, con colores blanco y negro. Luego se mete en un bucle sin fin, en el cual cambia el color del borde, escribe un patrón de bytes a memoria de pantalla (escribe un byte con todos sus bits a 0 excepto uno, que lo pone a 1. De esa forma puedo averiguar qué bit es el que falla en una memoria y así reemplazarla). En cada vuelta del bucle, el bit que está a 1 se va desplazando de derecha a izquierda para probar todos los bits de un byte. Luego, se hace lo mismo pero en "video inverso", es decir, se escribe un byte con todos sus bits a 1 excepto uno que se pone a 0. Después de rellenar la pantalla, hago sonar el generador de sonidos con un pitido de 1kHz. Luego espero un segundo, y vuelta a empezar con otra vuelta del bucle. Con la EPROM del Burnin Rubber borrada (por supuesto hice copia de seguridad de su contenido :)) y vuelta a grabar con este mini test, hice las siguientes pruebas:
En los casos 3 y 4, el desvanecimiento es a blanco (píxeles a 1). Lógico que éste sea el resultado final cuando no hay memoria, ya que es sinómino de "bus flotante" pero me extraña que tarde tanto, y que haya algo así como una persistencia de los valores en los 16K de pantalla, cuando no existen esos 16K. Lo que está claro es que el acceso a memoria no es igual con ACID y sin ACID. En este último caso, los valores que escribo en RAM (posiciones C000 a FFFF) no los veo, pero puede ser por alguna de estas razones:
Durante la ejecución del pequeño test, se llena la pantalla con un patrón que difiere cada vez en un bit. En este caso se ve que se ha llenado la pantalla con un patrón de bytes que contiene todos sus bits a 1 excepto uno (el más significativo quizás a juzgar por lo cercano a la izquierda del borde que están las líneas) que está a 0. El color para el pixel apagado es negro, y para el pixel encendido es 1. Más concretamente, las pantallas que van apareciendo en el test son consecuencia de llenar la memoria de pantalla (C000 a FFFF) con los siguientes valores, uno en cada vuelta del bucle: 01h, 02h, 04h, 08h, 10h, 20h, 40h, 80h, FEh, FDh, FBh, F7h, EFh, DFh, BFh, 7Fh, y vuelta a empezar con 01h... La parte del programa que se encarga de volcar una de estas secuencias a la pantalla es ésta: ld hl,&c000 ex af,af' ld (hl),a rlca ex af,af' ld de,&c001 ld bc,16383 ldir El registro A' contiene el próximo valor de la secuencia. Lo que he hecho entonces es aplicar el analizador lógico y programarlo para que comience la captura en cuanto se encuentre con un flanco negativo en la señal de escritura WE de cualquiera de las memorias dinámicas. Esto hará que lo primero que se vea sea la escritura que se hace en la instrucción LD (HL),A y poco después, un tren de escrituras periódico, causados por las sucesivas ejecuciones de LDIR. Atención al método que uso para rellenar la pantalla. Seguro que todos lo conoceis: es simplemente un LDIR que escribe en la siguient posición de memoria lo que hay en la actual. Lo recalco aquí porque el hecho de usar este método es lo que me ha permitido averiguar lo que he averiguado... Para empezar, un vistazo general. Esto es lo que se ve cuando el ACID está puesto.
La última señal es WE (Write Enable) y se ve perfectamente como hay una primera escritura, y después una serie de ellas con la misma duración una de la siguiente. La primera escritura es la de LD (HL),A y las siguientes, las de LDIR. Vemos también que SIN cambia de forma aparentemente errática. No se observa ningún patrón. CCLR no se muestra aquí, pero está permanentemente en estado alto. El analizador me permite colocar una serie de cursores, que no son más que marcas temporales, para las cuales puedo visualizar el valor de todas las señales en el instante en que esa marca se produjo. Los cursores son A,B,C,D y E. A está colocado en el momento en que el Z80 escribe el dato de LD (HL),A . El valor de RAMDATA es el valor que la memoria "ve", y es el que realmente se va a escribir en ella. B está colocado en el momento en que el Z80 lee un valor de memoria dentro de la primera iteración de la instrucción LDIR. Recordad que LDIR puede descomponerse internamente en las siguientes instrucciones: ld reg,(hl) ld (de),reg inc hl inc de dec bc si bc<>0 vuelve a empezar Donde "reg" es un registro interno. No se usa el acumulador en esta instrucción. Pues bien. El cursor B está situado en el momento en que se hace LD reg,(HL) por primera vez. C está colocado en el momento en que se ejecuta LD (DE),reg en la primera iteración de LDIR. D y E están situados en la lectura y escritura posterior, de la segunda iteración de LDIR. Recalco que el valor RAMDATA es el valor que la memoria "ve" desde el Z80 (mejor dicho, desde el gate array) cuando se escribe algo en ella. En una operación de lectura, es el valor que la RAM entrega al gate array para que éste se lo dé al Z80. Esto es importante recordarlo para lo que viene después. Seguimos: otro vistazo general, esta vez sin el ACID puesto.
SIN no se muestra aquí porque tiene un comportamiento errático, que bien pudiera deberse a ruido, con transiciones más rápidas que el propio reloj. Sin embargo CCLR tiene un comportamiento mucho más predecible, como se puede observar. Ahora vamos a ver qué pasa con la memoria, paso a paso. 1. Ejecución de LD (HL),A . A contiene un valor de la secuencia 01h, 02h, 04h, 08h, 10h, 20h, 40h, 80h, FEh, FDh, FBh, F7h, EFh, DFh, BFh, 7Fh Con ACID:
Sin ACID:
Aparentemente, en ambos casos la escritura se produce con normalidad. El ciclo de escritura RAS-WE-CAS es correcto, y el dato que la memoria ve (FDh) es el esperado. 2. Entramos en LDIR y se ejecuta la primera iteración de esta macroinstrucción: se lee de memoria un valor desde la dirección contenida en el registro HL (que es la misma dirección donde acabamos de escribir FDh), y justo después se escribe ese valor en la dirección dada por DE. Estos dos instantes están marcados por los cursores B y C. Con ACID:
Sin ACID:
Aquí comienzan las diferencias. El analizador muestra (ver cursor B) que en ambos casos se ha accedido a la dirección marcada por HL y se ha leido un valor correcto (FDh). Pero a la hora de escribir (cursor C), con el ACID se ve cómo se escribe el valor FDh (que se acaba de leer en B), y sin embargo, sin el ACID, el valor que se escribe es uno muy distinto (B0h). ¿Cómo es posible esto? Hemos visto (cursor A) que el Z80 escribe correctamente valores en memoria, y que esos valores son leidos de la misma (cursor B). Entonces, ¿de dónde viene el valor que se escribe en el cursor C? Muy sencillo: recordad que lo que estamos viendo en el analizador son los valores que la memoria RAM "ve". Nadie ha asegurado que esos mismos valores son los que el Z80 ve. Y ahí está todo el quid de la cuestión: EL GATE ARRAY ESTA ENGAÑANDO AL Z80. EN LECTURA, NO LE DEVUELVE LOS VALORES REALES DE LA MEMORIA. El Z80 no recibe en B el valor FDh que la memoria le entrega, sino que recibe "de parte del gate array", el valor B0h (parece ser que siempre es este mismo valor). De tal forma, que obediente, cuando va a escribir en memoria el valor que ha "leido", escribe B0h (cursor C). 3. Segunda iteración de LDIR. Dado que cada iteración de LDIR lee el valor que se escribió en la anterior, y lo escribe en la actual, en esta segunda iteración, lo que se lee de memoria será el valor B0h que la CPU escribió en el cursor C. Esto es lo que pasa en el cursor D. En el cursor E, al igual que en C, el Z80 escribe el valor que ha leido, que no es otro que B0h. Aquí muestro solamente el cronograma con el ACID quitado.
Del análisis de estos cronogramas se deduce el siguiente comportamiento cuando no está el ACID puesto:
Ahora entramos en el terreno de las hipótesis: Suponiendo que el ACID realmente sólo afecta a las lecturas que el Z80 hace a memoria en la manera en que he comentado, la forma de anular este comportamiento consiste en añadir dos transceivers al bus de datos del Z80. La dirección de los transceivers estará gobernada por las señales RD y WR. Uno de los transceivers conectara el bus de datos del Z80 directamente con el bus de datos de la RAM. Este transceiver sólo se habilitará cuando se detecte una lectura a memoria RAM. El otro transceiver conectará el bus de datos del Z80 con el bus de datos "oriignal" que va a los pines de datos del Z80. Se habilitará en todos los demás casos en los que no se habilite el otro transceiver, es decir, cuando se escriba a memoria, o cuando se haga cualquier operación de E/S. Aún así, faltaría solucionar un problema: ¿cómo detectamos cuando se está leyendo realmente de memoria RAM? La cosa no es trivial, ya que una operación de lectura de memoria debe tener en cuenta:
El hecho de que el ACID use A0-A7 como entrada me resulta curioso... sobre todo porque acabo de caer en la cuenta de que en el Z80, A0-A7 tienen un uso muy concreto dentro del ciclo M1: es la dirección de fila de refresco de la RAM. En otras palabras, durante el ciclo de refresco, A0-A7 se comporta como un contador de 0 a 127 y vuelta a empezar. ¿Tendrá algo que ver en el ACID? Hay una forma de comprobarlo: A7 siempre tiene el valor 0 durante el ciclo de refresco. Si al retirar A7 del ACID, éste sigue funcionando, entonces quizás tenga que ver.
Mientras tanto, he pensado en un "workaround", un parche feo, tosco y nada elegante, pero que podría servir. La idea se basa en suponer que el ACID unicamente "estropea" los accesos a lectura desde el Z80 a la memoria. También se basa en que el cronograma de dichos accesos sigue un patrón fácilmente reconocible, y es éste:
El ciclo comienza en el punto donde está el cursor F, es decir, cuando baja MREQ, y termina cuando sube esta misma señal. En todos los accesos de lectura a memoria he observado que se sigue este patrón temporal:
Si este patrón es consistente y único, es posible diseñar una máquina de estados cuyas entradas sean MREQ, RAS, CAS, y WE, y cuya salida sea una señal de ENABLE para un transceiver (que supondré activa a nivel bajo). El diagrama de estados de este chip que llamaremos chip ALMAX podría ser éste:
La salida de este chip, como digo, estaría ligada a un transceiver que uniría D0-D7 en la RAM con D0-D7 en el Z80. Los mismos pines D0-D7 del Z80 estarían unidos al bus de datos original desacoplándolo mediante resistencias de 220 ohmios. Cuando el transceiver está en alta impedancia, el Z80 ve los datos D0-D7 que le llegan del bus de datos original. Cuando el transceiver está activo, sus líneas de datos, unidas directamente al Z80, tienen más prioridad que las del bus original, y será entonces el dato que llegue de RAM al transceiver el que el Z80 vea. Esta máquina de estados deberá tener un reloj rápido. El intervalo más corto entre transiciones es el que se produce desde que baja MREQ hasta que baja RAS, y eso son 40ns, con lo que, según el teorema del muestreo, el chip debería realizar transiciones en la máquina de estados cada 20ns. Eso significa usar un reloj de 50MHz. Una GAL16V8 sería más que suficiente para implementar esta máquina, y además permite frecuencias de reloj de más de 100MHz. Dos días más tarde, constataba los malos augurios sobre el comportamiento del ACID. El "ALMAX" no serviría de gran cosa... A partir de la información de Arnoldemu, he escrito un nuevo programa que escribe y lee de ciertas posiciones de memoria, tanto con el ASIC mapeado como sin mapear. Este es un fragmento del mismo, concretamente en el que se testean algunas posiciones dentro del rango 4000-7FFF con el ASIC mapeado, esto es, los valores leidos y escritos no lo son de la memoria, sino que están dentro de propio ASIC. Como las sondas para el bus de datos las tengo en D0-D7 pero de la RAM dinámica, para poder ver qué estoy leyendo del ASIC lo que hago es escribir una "copia" del valor leído a una posición de memoria fuera del espacio mapeado por el ASIC. Esa escritura provoca un flanco negativo en WE, y esa condición es la que me vale para poder ver, ya en el bus de datos de la memoria, el valor que acabo de leer del ASIC en una instrucción anterior. Por eso hay instrucciones del tipo LD (DE),A que son las que me permiten ver los valores interesantes. Los seis cursores del analizador están apuntando precisamente a las seis escrituras que se hacen en el siguiente fragmento de código: ;page in asic ram between &4000-&7ffff
ld bc,&7fb8
out (c),c
ld de,&c000
ld a,&55
ld (de),a ;First write, to trigger the logic analyzer. 55h is written to RAM
ld hl,&4000
ld (hl),&cc
ld a,(hl) ;A read here return only the low nibble
ld (de),a ;This has to write 0Ch into RAM.
ld hl,&6000
ld (hl),&77
ld a,(hl) ;A read here returns the whole data
ld (de),a ;This has to write 77h into RAM.
ld hl,&6001
ld (hl),&82
ld a,(hl) ;This read returns 02h (low nibble? or data & 03h?)
ld (de),a ;This has to write 02h into RAM.
inc a
ld (hl),a ;03h is written, so...
ld a,(hl) ;... a read will return FFh, as Arnoldemu doc states.
ld (de),a ;This has to write FFh into RAM.
ld hl,&6800
ld (hl),&aa
ld a,(hl) ;A read here returns invalid data (7Eh)
ld (de),a ;This has to write 7Eh into RAM.
Con el ACID puesto, obtenemos los siguientes valores en los 6 cursores (en este caso no pondré el cronograma completo, porque no hace falta ver el ciclo de escritura completo, sólo nos interesa qué dato se escribe en RAM):
Sin el ACID sin embargo, obtenemos lo siguiente :(
Sólo la primera escritura coincide, que es la que hace la CPU con un dato fijo, a memoria RAM. Esto ya vimos en el test anterior que funcionaba incluso sin el ACID. Pero el resto de accesos a memoria, que están interactuando con el ASIC (sprites, DMA, etc.) no devuelven en lectura el valor esperado, sino que devuelven lo que Arnoldemu llama "valor inválido", es decir, como si en esa zona no hubiera nada mapeado por el ASIC. Esto puede ser porque:
Me inclino por este último comportamiento, aunque como en realidad no he hecho "nada" visible con el ASIC (como por ejemplo, poner un sprite en pantalla) no puedo asegurar que sólo pase esto último. ¿Alguien tiene un código para poner un sprite hardware en pantalla? Más tarde conseguí escribir un programa que mueve un sprite por la pantalla usando el hardware del CPC+ y confirmé este punto. Incluso aunque sólo hubiera problemas para leer y no para escribir, cualquier programa preparado para CPC464+ que necesite leer datos de la zona mapeada por el ASIC, fallará si no está el ACID puesto. Conclusión: LA NO PRESENCIA DEL CHIP ACID HACE QUE EL ASIC INTERCEPTE CUALQUIER ACCESO DE LECTURA A RAM, YA SEA RAM DE VERDAD, O REGISTROS DEL ASIC MAPEADOS A RAM, Y ENTREGUE DATOS FALSOS A LA CPU. Y esto es lo malo: al contrario de la memoria RAM, que podíamos hacerle un bypass al ASIC leyendo directamente de ella, no hay forma de acceder a la memoria interna del ASIC si el propio ASIC no nos deja. Con lo que está claro que para obtener un CPC464+ plenamente funcional, no vale la solución que propuse. Hay que desentrañar el comportamiento del ACID y clonarlo. Con el ALMAX como mucho se podrá asegurar que el ordenador funcione en modo compatible con el CPC. Se podrían usar las características del CPC+ (tendría que comprobarlo si alguien me pasa un código que haga algo que sólo puede hacerse con un CPC+) pero con la condición de que el código no necesite leer de la zona mapeada por el ASIC.
Y hasta aquí llegué. El resto de las conversaciones en el hilo se centraron en buscar a "alguien" que hubiera trabajado en el diseño del ASIC y del ACID, y nos pudiera dar información relevante sobre cómo funciona el mismo. No tuvimos suerte. Mientras tanto, No$Cash consiguió lo imposible...
Intercambié algunos correos con No$Cash ya que él había destripado el algoritmo pero no tenía claras algunas de las señales, ya que no uso un CPC+ conectado al ACID. Le pasé algunos cronogramas pero no era lo que él estaba buscando. Le prometí que en cuanto pudiera volvería a pinchar el analizador lógico en el CPC+. La cosa se demoró casi un año (en medio de todo esto me casé, con lo que todo ello conllevó.
Y así llegamos al mes de Marzo de este año 2011. Con algo de experiencia en Verilog (un lenguaje HDL de amplia difusión en Estados Unidos) recuperé la placa semidesnuda del CPC+, el analizador lógico, y el cartucho del Brnin' Rubber con la intención de obtener gráficas detalladas del comportamiento en los flancos de las señales SIN, CLK y CCLR, y así poder responder a las preguntas de No$Cash. Le mandé la información y le pregunté que cómo es que no había noticias de una implementación hardware del ACID. No me llegó a responder. Buscando en Internet, topé con una galería de fotos de una retroparty donde se mostraba a dos personas (Octoate y Nilquader) con un CPC+ que al parecer habían conseguido clonar el ACID. Extrañamente no había rastro de código HDL por ninguna parte.
Uno de los hackers, Octoate, ha publicado recientemente toda la información que consiguieron en Octubre del 2010, cuando clonaron el ACID. ¡¡¡BIEN por ellos!!! :) Ya que no había nada publicado, quise probar suerte. Lamentablemente, el único cartucho de que disponía estaba muy perjudicado con tanto soldar y desoldar. Para colmo, enredar con las sondas del analizador lógico hacía el sistema aún más inestable. Resolví crear un cartucho de diagnóstico, que me permitiera llevar a cabo comodamente cualquier experimento que necesitara.
La tira de pines más larga contiene todas las señales del ACID, y me permite mirar la evolución de las mismas en el analizador lógico. A su derecha, tres pines me permiten elegir con un jumper si la CPLD se va a alimentar con 5 voltios (familia XC9000 de Xilinx) o con 3.3V (familia XC9500XL). En el costado izquierdo, el JTAG para programar la CPLD, y en la parte de abajo, otros tres pines que me permiten, usando otro jumper, elegir si la señal SIN proviene del ACID "de verdad" o de la CPLD. Completa el cartucho un zócalo grande para la EPROM, uno pequeño para el ACID, y otro PLCC44 para la CPLD. Con él sí que llegué a clonar el chip ACID, usando como base el algoritmo de No$Cash. La descripción en Verilog a la que llegué es la siguiente:
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: Dept. Architecture and Computer Technology. University of Seville.
// Engineer: Miguel Angel Rodriguez Jodar.
//
// Create Date: 13:08:05 04/01/2011
// Design Name:
// Module Name: acid
// Project Name:
// Target Devices: XC9536 / XC9536XL y superiores (tolerantes a 5V)
// Tool versions: ISE Webpack 12.4
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module genxorvals (
input [7:0] a,
output [16:0] cmpval,
output [16:0] xorval
);
//CmpVal=13596h == 1 0011 0101 1001 0110
assign cmpval[0] = a[5];
assign cmpval[1] = ~a[5];
assign cmpval[2] = ~a[0];
assign cmpval[3] = a[0];
assign cmpval[4] = ~a[3];
assign cmpval[5] = a[3];
assign cmpval[6] = a[2];
assign cmpval[7] = ~a[2];
assign cmpval[8] = 1'b1;
assign cmpval[9] = a[6];
assign cmpval[10] = ~a[6];
assign cmpval[11] = a[7];
assign cmpval[12] = ~a[7];
assign cmpval[13] = ~a[1];
assign cmpval[14] = a[1];
assign cmpval[15] = a[4];
assign cmpval[16] = ~a[4];
//XorVal=0c820h == 0 1100 1000 0010 0000
assign xorval[1:0] = 2'b00;
assign xorval[2] = a[0];
assign xorval[4:3] = 2'b00;
assign xorval[5] = ~a[3];
assign xorval[6] = 1'b0;
assign xorval[7] = a[2];
assign xorval[10:8] = 3'b000;
assign xorval[11] = ~a[7];
assign xorval[12] = 1'b0;
assign xorval[13] = a[1];
assign xorval[14] = ~a[1];
assign xorval[15] = ~a[4];
assign xorval[16] = 1'b0;
endmodule
module acid (
input clk,
input [7:0] a,
input ce,
input cclr,
output sin
);
reg [16:0] sreg = 17'h1FFFF;
wire [16:0] xorval;
wire [16:0] cmpval;
wire [16:0] sregxored;
wire newbit;
genxorvals generador (a, cmpval, xorval);
assign newbit = ^{sreg[0],sreg[9],sreg[12],sreg[16]};
assign sregxored = sreg ^ xorval;
assign newbitxored = ^{sregxored[0],sregxored[9],sregxored[12],sregxored[16]};
assign sin = sreg[0];
always @(negedge clk)
if (!cclr)
sreg <= 17'h1FFFF;
else if (!ce && ((sreg | 17'h00100) == cmpval))
sreg <= {newbitxored,sregxored[16:1]};
else
sreg <= {newbit,sreg[16:1]};
endmodule
Este es el resultado: el cartucho de diagnóstico ejecutando Pinball Magic, usando a una XC9572 (el código se puede sintetizar en una XC9536) como chip ACID. El propio chip ACID es testigo de todo, deconectado y puesto lejos de la placa :D
Algunos datos técnicos de la implementación. La herramienta ISE de Xilinx da los siguientes reportes: Fitter Report
RESOURCES SUMMARY
PIN RESOURCES
GLOBAL RESOURCES
POWER DATA
Si alguien se pregunta si podría construirse un clon del ACID usando chips discretos, pues... esto es lo que aparece en el informe de síntesis sobre los recursos utilizados, incluyendo qué tipo de puertas lógicas se han usado y cuántas. ========================================================================= * Final Report * ========================================================================= Final Results RTL Top Level Output File Name : acid.ngr Top Level Output File Name : acid Output Format : NGC Optimization Goal : Speed Keep Hierarchy : Yes Target Technology : XC9500 CPLDs Macro Preserve : YES XOR Preserve : YES wysiwyg : NO Design Statistics # IOs : 12 Cell Usage : # BELS : 150 # AND2 : 29 # AND3 : 1 # AND8 : 2 # INV : 68 # OR2 : 21 # OR3 : 7 # XOR2 : 22 # FlipFlops/Latches : 17 # FD : 17 # IO Buffers : 12 # IBUF : 11 # OBUF : 1 ========================================================================= Otra de las herramientas que ofrece el ISE de Xilinx e suna vista del esquemático equivalente del chip. Puede verse por niveles. Así, el primer nivel, en que se muestra el chip completo como una caja negra, es éste:
Parece tan enigmático como el chip original, ¿no? Haciendo doble clic en una esquina de la "caja negra", el ISE nos desvela la circuitería que ha sintetizado (clic en la imagen para verla a tamaño completo)... |


















