Editor de esquematicos en ISE

Entorno de desarrollo para Spartan 6 e inferiores (también soporta algunas Artix 7)
Responder
BCH
Veroboard
Mensajes: 2
Registrado: 17 Ago 2018, 11:00

Editor de esquematicos en ISE

Mensaje por BCH » 29 Ago 2018, 15:50

Buenas!

Me he puesto a hacer algunos intentos en el ISE. Como no tengo nada de idea de vhdl o verilog he comenzado haciendo algo con el editor de esquematicos que viene en el ISE.

He hecho un circuito bastante simple, lo he sintetizado y programado en una XC9536 y me ha funcionado a la perfeccion! :D

Ahora estaba intentando algo un poco mas complejo pero no consigo que me lo sintetize. El esquema en cuestion es un expansor de slot para MSX:

Imagen

Creo que lo que da problema son las salidas del 74xx273 que van al 74xx240.

Alguna idea?

Avatar de Usuario
jepalza
Spartan 3
Mensajes: 226
Registrado: 14 Ago 2018, 18:51

Re: Editor de esquematicos en ISE

Mensaje por jepalza » 29 Ago 2018, 17:44

Si quieres un buen ejemplo (liado, pero bueno) mira los fuentes del Amstrad CPC de Renaud Helias. Las primeras versiones son en su gran mayoría, esquemas. Yo aprendí un montón sobre su funcionamiento cuando hice la conversión al ZXUNO del CPC 6128. Las útlimas versiones, los esquemas han ido desapareciendo hasta convertirse en VHDL convertido desde los esquemas, que es aún mas liante de entender.

carmeloco
GAL
Mensajes: 51
Registrado: 20 Ago 2018, 15:32

Re: Editor de esquematicos en ISE

Mensaje por carmeloco » 29 Ago 2018, 19:13

Yo tengo hecho un expansor de slots de MSX, pero con integrados 74LSXX.
En ese esquema que has puesto, es fácil cometer un error, que hace que no funcione. En el esquema, parece que los buses de direcciones D0 a D7 del U5, vengan del MSX, y que los del U4, vayan hacia los slots espandidos, pero no es así. Los buses de direcciones, son comunes en todo el expansor, de forma que los D0 a D7 de U5, tienen que ir unidos a los D0 a D7 de U4, y también tienen que ir unidos tanto al slot del MSX como a los slots expandidos.

Mira a ver si es por eso que no te funciona.

Avatar de Usuario
mcleod_ideafix
Site Admin
Mensajes: 80
Registrado: 14 Ago 2018, 01:15

Re: Editor de esquematicos en ISE

Mensaje por mcleod_ideafix » 31 Ago 2018, 15:14

El esquema que has puesto no es complejo, y poder se puede hacer con el editor de esquemáticos. Lo que pasa es que el editor no tiene circuitos estándar TTL, así que algunos tienes que "inventártelos" tú en base a las primitivas que te dan.

De todas formas, te doy una posible implementación en Verilog, comentada, para que veas un poco la relación entre ese texto que parece código (pero que no lo es), y tu circuito.

Este circuito tiene de todas formas una peculiaridad: usa un registro disparado por flanco de reloj, pero el reloj no es tal reloj, sino una señal generada mediante lógica combinacional en otra parte del circuito. Esto es válido cuando diseñas con circuitos discretos TTL "físicos", pero usando FPGAs y CPLDs.... hay limitaciones.

No es una limitación de Verilog o VHDL, en donde se puede usar como reloj la señal que te dé la gana, pero no todo lo que escribes en Verilog o VHDL puede convertirse a un circuito con las tecnologías (FPGA y CPLD) que manejamos. Con otras (ASIC), a lo mejor sí.

Bueno, pues mi propuesta de implementación, siguiendo lo más fielmente posible el esquemático que has puesto, es ésta:

https://www.edaplayground.com/x/596R

Código: Seleccionar todo

// Code your design here

module simple_slot_expander_for_msx_computer (
  input wire reset_n,
  input wire sltsel,
  input wire [15:0] a,
  input wire rd_n,
  input wire wr_n,
  inout wire [7:0] d,
  output wire [3:0] exp,
  // mirar descripción para entender estas dos señales a continuación
  input wire clk_u5,
  output wire salida_y2
);
  
  // U1 y U2: parte del decodificador de direcciones
  wire salida_u1 = ~(a[0] & a[1] & a[2] & a[3] & a[4] & a[5] & a[6] & a[7]);
  wire salida_u2 = ~(&a[15:8]);  // una forma más corta de poner lo anterior
  
  // U3 es un decodificador, del cuál sólo usamos Y1 e Y2.
  // Y1 vale 0 cuando CBA = 001. en otro caso vale 1
  // Y2 vale 0 cuando CBA = 010, en otro caso vale 1
  // Si G2A o G2B valen 1, Y1 e Y2 valen 1. G2A es salida_u1, G2B es salida_u2
  
  // Conectamos salida_u1 y salida_u2 a g2a y g2b;
  wire g2a = salida_u1;
  wire g2b = salida_u2;
  
  // Conectamos las señales pertinentes a las entradas a, b Y c del decodificador U3
  wire u3_a = wr_n;  // le pongo el u3_ por delante porque ya existe una señal que se llama "a"
  wire u3_b = rd_n;
  wire u3_c = sltsel;
  
  reg y1, y2;
  always @* begin
    if (g2a == 0 && g2b == 0) begin  // si tanto G2A como G2B valen 0, entonces...
      if ({u3_c, u3_b, u3_a} == 3'b001)  // miramos a ver si CBA valen 001
        y1 = 0;
      else
        y1 = 1;
      if ({u3_c, u3_b, u3_a} == 3'b010)
        y2 = 0;
      else
        y2 = 1;
    end
    else begin  // si G2A vale 1, o G2B vale 1, ambas salidas Y0,Y1 valen 1
      y1 = 1;
      y2 = 1;
    end
  end
  
  // U5 es un poco truculento. Es un registro controlador por flanco de subida de
  // reloj, pero el reloj proviene de un circuito combinacional (y2). Aquí lo
  // ideal sería tener un reloj aparte (el de la CPU, por ejemplo) y usar y2
  // como clock enable. Lo más probable es que una CPLD no permita rutar una
  // señal producida en su propio interior como reloj (una FPGA puede, con reservas)
  // así que voy a optar por lo siguiente: sacar y2 hacia afuera por una patilla de la
  // CPLD, y fuera de la CPLD, se unirá esa patilla con una patilla de entrada de
  // reloj para poder usarlo como reloj en U5.
  
  assign salida_y2 = y2;  // sacamos y2 hacia una salida
  
  // Registro U5 74LS273
  reg [7:0] q_u5;
  always @(posedge clk_u5) begin   // clk_u5 es salida_y2, unidas por fuera
    if (reset_n == 1'b0)
      q_u5 <= 8'b00000000;
    else
      q_u5 <= d;
  end
  
  // U4 es un driver triestado. Se usará para leer lo que haya guardado en U5
  // cuando el Z80 pida leerlo. La señal de lectura es y1. Cuando y1 = 0, lo que
  // haya en q_u5 se vuelca al bus de datos. Si no, se queda en alta impedancia
  
  // Implementación de U4:
  assign d = (y1 == 0)? q_u5 : 8'bzzzzzzzz;
      
  // U6 son dos multiplexores que comparten sus entradas de control A,B.
  // Cada mux permite elegir una de 4 entradas, a su salida. La entrada
  // se selecciona con el valor de dos bits B,A (en ese orden)
  // Las entradas a los dos muxes vienen de la salida del registro U5
  reg salida_mux1, salida_mux2;
  wire [1:0] control_mux = {a[15], a[14]};  // a15 va a B, y a14 a A, así que
                                            // esas dos señales controlan el mux
  always @* begin
    case (control_mux)  // un case viene perfecto para implementar un mux
      2'b00: 
        begin
          salida_mux1 = q_u5[0];
          salida_mux2 = q_u5[1];
        end
      2'b01:
        begin
          salida_mux1 = q_u5[2];
          salida_mux2 = q_u5[3];
        end
      2'b10:
        begin
          salida_mux1 = q_u5[4];
          salida_mux2 = q_u5[5];
        end
      2'b11:
        begin
          salida_mux1 = q_u5[6];
          salida_mux2 = q_u5[7];
        end
      default:  // siempre hay que poner un default para evitar que el sintetizador infiera un
        begin   // latch, salvo que se le diga que asuma que todos los cases son completos
          salida_mux1 = 0;
          salida_mux2 = 0;
        end
    endcase
  end
  
  // U7A es un pequeño deco, pero además de sus entradas de datos y control, necesita una
  // entrada de habilitación que proviene de un circuito que detecta si la dirección
  // es $FFFF. Esta condición es muy fácil de poner en Verilog, pero como quiero respetar
  // al máximo el circuito original, lo haré usando las puertas lógicas de U8 que hay dibujadas
  // en la parte inferior del circuito
  
  wire salida_u8a = ~(salida_u1 | salida_u2);
  wire salida_u8b = ~(sltsel | salida_u8a);
  wire salida_u8c = ~salida_u8b;  // Una puerta NOR con las entradas unidas es un inversor
  
  // Ahora sí, implemento U7A
  reg [3:0] y_u7a;  // su salida son 4 bits, Y3 a Y0
  wire [1:0] control_u7a = {salida_mux2, salida_mux1};  // Entradas B,A al deco
  always @* begin
    if (salida_u8c == 0) begin  // salida_u8c está conectada a la entrada G de habilitacion
      case (control_u7a)
        2'b00: y_u7a = 4'b1110;
        2'b01: y_u7a = 4'b1101;
        2'b10: y_u7a = 4'b1011;
        2'b11: y_u7a = 4'b0111;
        default: y_u7a = 4'b1111;  // siempre ponemos un default
      endcase
    end
    else
      y_u7a = 4'b1111;  // si la entrada de habilitacion es 1, se desactivan todas las salidas
  end
  
  // conecto y_u7a a las salidas EXP
  assign exp = y_u7a;
endmodule
Si pulsas en el enlace anterior verás este mismo diseño en el EDA Playground. Pulsando Run verás el circuito que genera. No se parece en nada al que has puesto, o al menos esa es la primera impresión. Sin embargo, mirando el detalle podemos ver algunas cosas interesantes:
extracto_circuito.png
extracto_circuito.png (38.7 KiB) Visto 9488 veces
Esto es la circuitería que va conectada al bus de datos. El propio bus de datos aparece referenciado en el centro (octógono con la letra "d": los octógonos representan pines de entrada o salida del circuito). El bus de datos tiene a su derecha en este extracto a dos circuitos: un multiplexor y un biestable. Entre esos dos circuitos forman en realidad el circuito original U5. El mux lo que hace es implementar la señal CLR de borrado: si reset_n vale 0, lo que se presenta a las entradas del registro es 00000000, en otro caso, es el valor de "d".
El registro como tal es el $dff (flip flop tipo D) que hay a la derecha del mux. Tiene un reloj, que ya hemos visto cuál es y de dónde viene, y una entrada D que viene de lo que el mux le haya dado. La salida Q es la señal q_u5, a la derecha del todo. Esa señal contiene siempre el valor almacenado en el registro.

Por otra parte, mirando a la izquierda del todo, vemos otro mux. Este es el driver triestado formado por U4A y U4B, que permite al Z80 leer el valor de lo que haya en U5. Recordemos que la entrada G de este driver está unido a Y1. Si Y1 vale 0, lo que sale por el mux hacia el bus de datos (señal "d") es lo que haya en su entrada A (que proviene de la señal q_u5). Si no, el mux entrega la condición "alta impedancia", o lo que es lo mismo, la salida Y del mux se pone en alta impedancia.

Avatar de Usuario
mcleod_ideafix
Site Admin
Mensajes: 80
Registrado: 14 Ago 2018, 01:15

Re: Editor de esquematicos en ISE

Mensaje por mcleod_ideafix » 31 Ago 2018, 16:41

Lo anterior sería un código al nivel RTL (especificar señales y cómo se conectan unas a otras). Habitualmente se trabaja a nivel de comportamiento (se especifica cómo se comporta el circuito y se deja al sintetizador que infiera las señales adecuadas).

Una versión, para mi gusto más elegante y probablemente más optimizada en cuanto a recursos, quedaría así:

https://www.edaplayground.com/x/gQu

Código: Seleccionar todo

// Code your design here

module simple_slot_expander_for_msx_computer (
  input wire clk,     // reloj de la CPU, por ejemplo
  input wire reset_n, 
  input wire [15:0] a,
  input wire rd_n,
  input wire wr_n,
  input wire sltsel,
  inout wire [7:0] d,
  output reg [3:0] exp
);
  
  reg [7:0] registro_u5;  // el registro de 8 bits U5
  
  // Este always implementa U1, U2, y U3
  always @* begin
    enable_dout = 1'b0;
    load_registro_u5 = 1'b0;
    if (sltsel == 1'b0 && a == 16'hFFFF) begin
      if (rd_n == 1'b0 && wr_n == 1'b1)
        enable_dout = 1'b1;
      else if (rd_n == 1'b1 && wr_n == 1'b0)
        load_registro_u5 = 1'b1;
    end
  end
  
  // Esta asignacion continua implementa el driver triestado U4
  assign d = (enable_dout == 1'b1)? registro_u5 : 8'bzzzzzzzz;
  
  // Este always implementa el registro U5
  always @(posedge clk) begin
    if (reset_n == 1'b0)
      registro_u5 <= 8'b00000000;
    else if (load_registro_u5 == 1'b1)
      registro_u5 <= d;
  end
  
  // Implementación de U6: doble mux 4:1
  reg [1:0] salida_u6;
  always @* begin
    salida_u6 = 2'b00;
    case (a[15:14])
      2'b00: salida_u6 = registro_u5[1:0];
      2'b01: salida_u6 = registro_u5[3:2];
      2'b10: salida_u6 = registro_u5[5:4];
      2'b11: salida_u6 = registro_u5[7:6];
    endcase
  end
  
  // Implementación de U7 y U8
  always @* begin
    exp = 4'b1111;
    if (a != 16'hFFFF && sltsel == 1'b0) begin   // esta linea es U8
      case (salida_u6)
        2'b00: exp = 4'b1110;
        2'b01: exp = 4'b1101;
        2'b10: exp = 4'b1011;
        2'b11: exp = 4'b0111;
      endcase
    end
  end
endmodule

Avatar de Usuario
mcleod_ideafix
Site Admin
Mensajes: 80
Registrado: 14 Ago 2018, 01:15

Re: Editor de esquematicos en ISE

Mensaje por mcleod_ideafix » 31 Ago 2018, 19:58

Y para rizar el rizo, aquí tienes un testbench para comprobar que ambas versiones del diseño producen el mismo resultado. No es un testbench para comprobar que el diseño se ajusta a lo que dice que es, porque no conozco muy bien la arquitectura de expansores y mappers del MSX, sino para comprobar que ambas versiones son esencialmente lo mismo.

https://www.edaplayground.com/x/2wCz

Código: Seleccionar todo

module test_compara_dos_modulos;
  reg clkcpu;
  reg reset_n;
  reg sltsel;
  reg [15:0] a;
  wire [7:0] d_v1, d_v2;
  reg rd_n;
  reg wr_n;
  wire salida_y2;
  wire clk_u5;
  wire [3:0] exp_v1, exp_v2;
  
  reg [7:0] valor;  // prueba todos los valores de 0 a 255
  
  // Instanciamos las dos versiones del expansor de slot
  assign clk_u5 = salida_y2;  // la union de estas señales, fuera del módulo
  simple_slot_expander_for_msx_computer_v1 v1 (
    .reset_n(reset_n),
    .sltsel(sltsel),
    .a(a),
    .rd_n(rd_n),
    .wr_n(wr_n),
    .d(d_v1),
    .exp(exp_v2),
    .clk_u5(clk_u5),
    .salida_y2(salida_y2)
  );

  simple_slot_expander_for_msx_computer_v2 v2 (
    .clk(clkcpu),
    .reset_n(reset_n),
    .sltsel(sltsel),
    .a(a),
    .rd_n(rd_n),
    .wr_n(wr_n),
    .d(d_v2),
    .exp(exp_v1)
  );

  // Tratamos los inouts aqui
  assign #1 d_v1 = (wr_n == 1'b0)? valor : 8'hZZ;
  assign #1 d_v2 = (wr_n == 1'b0)? valor : 8'hZZ;
  
  initial begin
    $dumpfile ("dump.vcd");                    //
    $dumpvars (0, test_compara_dos_modulos);   // Interfaz con GTKWave
    
    clkcpu = 0;
    reset_n = 0;
    sltsel = 1;
    a = 0;
    rd_n = 1;
    wr_n = 1;
    
    valor = 0;  // de 0 a 255
    
    #1000;
    reset_n = 1;  // 1000 ns de reset
    
    repeat (256) begin
      // esperamos 2 ciclos de reloj
      repeat (2) @(posedge clkcpu);
      
      // Escribir un valor en el bus de datos
      a = 16'hFFFF;
      sltsel = 0;
      rd_n = 1;
      wr_n = 0;
      repeat (3) @(posedge clkcpu);  // 3 ciclos de escritura
      // Retiramos dato y terminamos ciclo de escritura
      wr_n = 1;
      sltsel = 1;
      @(posedge clkcpu);
      
      // Leemos valor recien escrito
      rd_n = 0;
      sltsel = 0;
      repeat (3) @(posedge clkcpu);  // 3 ciclos de lectura
      // Y terminamos el ciclo
      sltsel = 1;
      rd_n = 1;
      
      a = 0;
      sltsel = 0;  // vamos a probar EXP
      repeat (4) begin   // las 4 posibilidades de A15, A14
        repeat (2) @(posedge clkcpu); // cambiamos "a" cada 2 ciclos de reloj
        // durante estos dos ciclos, observar EXP
        
        a = a + 16'h4000;  // probamos otra combinacion de A15,A14
      end
      sltsel = 1;  // fin de la prueba de EXP
      @(posedge clkcpu);
      
      valor = valor + 1;  // a probar otro valor
    end
    
    $finish;
  end
  
  always begin
    clkcpu = #(1000.0/7) ~clkcpu;  // reloj de 3.5 MHz
  end
endmodule
Este testbench hace en esencia lo que dice este pseudocódigo:

Código: Seleccionar todo

Para valor = 0 hasta 255
  Escibe valor en el registro de los dos expansores
  Vuelve a leerlo
  Pon una detrás de otra, las 4 posibles combinaciones de A15,A14 en el bus para ver los valores de EXP
Finpara
En el enlace adjunto, abrid el testbench junto con las dos versiones del expansor en EDA Playground, y haced Run. Vereis el EPWave con el cronograma de los dos circuitos, y podreis comprobar visualmente que ambos producen el mismo resultado tanto en el bus de datos cuando se pide leer el valor escrito, como en los valores de las señales de EXP.

En otro testbench que es muy parecido a éste pondré cómo hacer que sea el propio testbench el que haga las comparaciones y nos avise si algo no concuerda.

Avatar de Usuario
mcleod_ideafix
Site Admin
Mensajes: 80
Registrado: 14 Ago 2018, 01:15

Re: Editor de esquematicos en ISE

Mensaje por mcleod_ideafix » 31 Ago 2018, 21:25

mcleod_ideafix escribió:
31 Ago 2018, 19:58
En otro testbench que es muy parecido a éste pondré cómo hacer que sea el propio testbench el que haga las comparaciones y nos avise si algo no concuerda.
Pues aquí está:

https://www.edaplayground.com/x/2_tX

Código: Seleccionar todo

module test_compara_dos_modulos;
  reg clkcpu;
  reg reset_n;
  reg sltsel;
  reg [15:0] a;
  wire [7:0] d_v1, d_v2;
  reg rd_n;
  reg wr_n;
  wire salida_y2;
  wire clk_u5;
  wire [3:0] exp_v1, exp_v2;
  
  reg [7:0] valor;  // prueba todos los valores de 0 a 255
  
  // Instanciamos las dos versiones del expansor de slot
  assign clk_u5 = salida_y2;  // la union de estas señales, fuera del módulo
  simple_slot_expander_for_msx_computer_v1 v1 (
    .reset_n(reset_n),
    .sltsel(sltsel),
    .a(a),
    .rd_n(rd_n),
    .wr_n(wr_n),
    .d(d_v1),
    .exp(exp_v2),
    .clk_u5(clk_u5),
    .salida_y2(salida_y2)
  );

  simple_slot_expander_for_msx_computer_v2 v2 (
    .clk(clkcpu),
    .reset_n(reset_n),
    .sltsel(sltsel),
    .a(a),
    .rd_n(rd_n),
    .wr_n(wr_n),
    .d(d_v2),
    .exp(exp_v1)
  );

  // Tratamos los inouts aqui
  assign #1 d_v1 = (wr_n == 1'b0)? valor : 8'hZZ;
  assign #1 d_v2 = (wr_n == 1'b0)? valor : 8'hZZ;
  
  initial begin
    clkcpu = 0;
    reset_n = 0;
    sltsel = 1;
    a = 0;
    rd_n = 1;
    wr_n = 1;
    
    valor = 0;  // de 0 a 255
    
    #1000;
    reset_n = 1;  // 1000 ns de reset
    
    repeat (256) begin
      // esperamos 2 ciclos de reloj
      repeat (2) @(posedge clkcpu);
      
      // Escribir un valor en el bus de datos
      a = 16'hFFFF;
      sltsel = 0;
      rd_n = 1;
      wr_n = 0;
      repeat (3) @(posedge clkcpu);  // 3 ciclos de escritura
      // Retiramos dato y terminamos ciclo de escritura
      wr_n = 1;
      sltsel = 1;
      @(posedge clkcpu);
      
      // Leemos valor recien escrito
      rd_n = 0;
      sltsel = 0;
      repeat (2) @(posedge clkcpu);  // 2 ciclos de lectura
      if (d_v1 != d_v2) $fatal (1, "ERROR lectura: d_v1 = %h . d_v2 = %h", d_v1, d_v2);
      @(posedge clkcpu);
      // Y terminamos el ciclo
      sltsel = 1;
      rd_n = 1;
      
      a = 0;
      sltsel = 0;  // vamos a probar EXP
      repeat (4) begin   // las 4 posibilidades de A15, A14
        @(posedge clkcpu); // cambiamos "a" cada 2 ciclos de reloj
        // durante estos dos ciclos, observar EXP
        if (exp_v1 != exp_v2) $fatal (1, "ERROR en EXP: exp_v1 = %b . exp_v2 = %b", exp_v1, exp_v2);
        @(posedge clkcpu);
        a = a + 16'h4000;  // probamos otra combinacion de A15,A14
      end
      sltsel = 1;  // fin de la prueba de EXP
      @(posedge clkcpu);
      
      valor = valor + 1;  // a probar otro valor
    end
    
    $display ("Test OK!");
    $finish;
  end
  
  always begin
    clkcpu = #(1000.0/7) ~clkcpu;  // reloj de 3.5 MHz
  end
endmodule

Responder

Volver a “ISE”