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 (38.7 KiB) Visto 9799 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.