En capítulos anteriores vimos como hacer una CPU sencilla, utilizando una ALU con un solo bit de control, capaz de realizar dos operaciones aritméticas; sumar y restar.
En este tutorial vamos a sustituir la ALU por otra con tres líneas de control con la que realizar ocho operaciones diferentes; dos aritméticas y 6 lógicas.
El código Verilog de la ALU queda como se muestra a continuación:
module sap_alu_v2r1 (opcore, A, B, DATA, opcore_ld, carry);
//input enable;
input [2:0] opcore;
input [3:0] A, B;
output reg [3:0] DATA;
// output status leds
//output [3:0] A_ld, B_ld;
output [2:0] opcore_ld;
output carry;
// aux regs
reg [4:0] alu_plus, alu_minus;
reg [3:0] alu_result;
reg C;
// output status leds assignments
//assign A_ld = A;
//assign B_ld = B;
assign opcore_ld = opcore;
assign carry = C;
//assign DATA = (enable) ? alu_result : 8'bZZZZ_ZZZZ;
always @(opcore, A, B)
case (opcore)
0: alu_plus = A + B;
1: alu_minus = A - B;
2: alu_result = A | B; // OR
3: alu_result = A & B; // AND
4: alu_result = A ^ B; // XOR
5: alu_result = ~(A | B); // NOR
6: alu_result = ~(A & B); // NAND
7: alu_result = ~(A ^ B); // XNOR
endcase
always @(opcore, A, B)
case (opcore)
0: DATA = alu_plus[3:0];
1: DATA = alu_minus[3:0];
2: DATA = alu_result;
3: DATA = alu_result;
4: DATA = alu_result;
5: DATA = alu_result;
6: DATA = alu_result;
7: DATA = alu_result;
endcase
always @(opcore, A, B)
case (opcore)
0: C = alu_plus[4];
1: C = alu_minus[4];
2: C = 1'bz;
3: C = 1'bz;
4: C = 1'bz;
5: C = 1'bz;
6: C = 1'bz;
7: C = 1'bz;
endcase
endmodule
Si prescindimos del bit de carry, se puede simplificar la ALU agrupando todas las operaciones en una sola declaración case.
module sap_alu_v2r1 (opcore, A, B, DATA, A_ld, B_ld, opcore_ld);
//input enable;
input [2:0] opcore;
input [3:0] A, B;
output reg [3:0] DATA;
output [3:0] A_ld, B_ld;
output [2:0] opcore_ld;
assign A_ld = A;
assign B_ld = B;
assign opcore_ld = opcore;
always @(opcore, A, B)
case (opcore)
0: DATA = A + B;
1: DATA = A - B;
2: DATA = A | B; // OR
3: DATA = A & B; // AND
4: DATA = A ^ B; // XOR
5: DATA = ~(A | B); // NOR
6: DATA = ~(A & B); // NAND
7: DATA = ~(A ^ B); // XNOR
endcase
endmodule
Partiendo de la CPU del capítulo 8, sustituimos el módulo de la ALU y el esquema queda como sigue:
El módulo top queda como se muestra a continuación:
module sap_cpu_top (clk, reset, a_latch, b_latch, c_latch, c_enable, a_enable, b_enable, data_in_enable, data_sw,
opcore, oc_ld, led0, led1, led2, led3, led6, led7, seg, an, alu_C);
input clk, reset; // generic inputs
input a_latch, a_enable; // a reg inputs
input b_latch, b_enable; // b reg inputs
input c_latch, c_enable; // ALU reg inputs
input data_in_enable;
input [7:0] data_sw;
input [2:0] opcore;
output alu_C;
output [6:0] seg;
output [3:0] an;
output led0, led1, led2, led3, led6, led7;
output [2:0] oc_ld;
wire [7:0] data_bus, a_reg_bus, b_reg_bus, ssd_bus, alu_reg_bus, alu_bus;
wire w30, w31;
assign led0 = data_in_enable;
assign led1 = a_enable;
assign led2 = b_enable;
assign oc_ld = opcore;
assign led6 = w30;
assign led7 = w31;
//******************
// Structural coding
//******************
// encoder para seleccion de bus de datos
encoder ENC (
.in0(data_in_enable),
.in1(a_enable),
.in2(b_enable),
.in3(c_enable),
.s0(w30),
.s1(w31));
// mux 4-1 para bus de datos
mux_bus MB (
.s0(w30),
.s1(w31),
.in3(alu_reg_bus),
.in2(b_reg_bus),
.in1(a_reg_bus),
.in0(data_sw),
.out(data_bus));
// a register
sap_register a_register (
.clk(clk),
.reset(reset),
.DATA_IN(data_bus),
.latch(a_latch),
.enable(a_enable),
.REG_OUT(a_reg_bus));
// b register
sap_register b_register (
.clk(clk),
.reset(reset),
.DATA_IN(data_bus),
.latch(b_latch),
.enable(b_enable),
.REG_OUT(b_reg_bus));
// ALU register
sap_register c_register (
.clk(clk),
.reset(reset),
.DATA_IN(alu_bus),
.latch(c_latch),
.enable(c_enable),
.REG_OUT(alu_reg_bus));
// ALU v2r1
sap_alu_v2r1 ALU (
.opcore(opcore),
.A(a_reg_bus),
.B(b_reg_bus),
.DATA(alu_bus),
.carry(alu_C),
.opcore_ld(oc_ld));
// mux for seven segment display
mux4_1 MUX41 (
.clk(clk),
.in_0(alu_reg_bus),
.in_1(b_reg_bus),
.in_2(a_reg_bus),
.in_3(data_bus),
.out(ssd_bus),
.gates(an));
// Seven segnent display driver
ssd_driver SSDD (
.in(ssd_bus),
.seg(seg));
endmodule
Archivo .XDC
Lo subimos a la BASYS 3 y comprobamos el funcionamiento.
Con los switches sw8-15 introducimos los datos en el bus y con los pulsadores L y C los cargamos en los registros A y B respectivamente. Con los switches sw1 y sw2 verificamos que los datos se han almacenado correctamente.
Con los switches sw4, 5 y 6 seleccionamos la operación a realizar y con el pulsador R se almacena el resultado en el registro C. Con el sw3 habilitamos el registro C y verificamos que el resultado es correcto.
Para mostrar correctamente los datos en los displays de 7 segmentos de la BASYS 3, sólo he tenido en cuenta los 4 bits menos significativos del bus.
CPU sencilla con FPGA v1.1:
Primera Parte: El bus de datos
Many thanks to Phil, for sharing his 8 bit computer project.
& 100 Random Task: Create a custom CPU from scratch
Puedes descargar los archivos Verilog de este tutorial aquí abajo.