Con este ejemplo queremos mostrar algunas características que puedan ser útiles en el diseño de sistemas más complejos; pero la arquitectura de este microprocesador (Multi-Cycle Processor) tiene poco que ver con las solicitadas en la tarea 3 fase 2 (single cycle processor) y 3 (pipeline processor) :
- Realización de un interfaz para modelar un bus triestado de interconexión de los diferentes elementos de un microprocesador.
- El bus triestado incomoda bastante a una FPGA y solo las células I/O de las FPGA tienen capacidad para implementarlo.
- El código es sintetizable en nuestra FPGA Cyclone V
- Determinación del funcionamiento del microprocesador mediante una especificación ASM (con 11 estados)
- Es un Multi-Cycle Processor ( se requieren 6 ciclos para completar cada instrucción) sin posibilidad de comenzar una nueva instrucción hasta que ha finalizado la anterior, en contraposición a los denominados «pipeline processors» como el que vais a diseñar en la fase 3
- Realización de un control cableado (FSM) en lugar de un control microprogramado
- Implementación de una memoria RAM en la que se almacena el programa y los datos.
La estructura del microprocesador que vamos a diseñar es la de la siguiente figura

Veamos como sería el código del interfaz y del top de la jerarquía.
Interfaz
El interfaz lo voy a utilizar tanto para la distribución de los datos (mediante un bus triestado denomindo sysbus) como para la distribución de las señales de control que irán desde el control path a cada unos de los elementos del data-path (PC,IR, ALU y RAM) y para la recepción de las señales de estado desde el control-path procedentes del data-path.
import cpu_defs::*;
interface CPU_bus (input logic clock, n_reset,
inout wire [WORD_W-1:0] sysbus);
//senyales de control
logic ACC_bus, load_ACC, PC_bus, load_PC, load_IR,
load_MAR, MDR_bus, load_MDR, ALU_ACC, ALU_add,
ALU_sub, INC_PC, Addr_bus, CS, R_NW;
//senyales de estado
logic z_flag;
logic [OP_W-1:0] op;
modport IR_port(input clock, n_reset,
input Addr_bus, load_IR,
inout sysbus,
output op);
modport RAM_port (input clock, n_reset,
input MDR_bus, load_MDR, load_MAR, CS, R_NW,
inout sysbus);
modport ALU_port (input clock, n_reset,
input ACC_bus, load_ACC, ALU_ACC, ALU_add, ALU_sub,
inout sysbus,
output z_flag);
modport PC_port (input clock, n_reset,
input PC_bus, load_PC, INC_PC,
inout sysbus);
modport seq_port (input clock, n_reset,
input op, z_flag,
output ACC_bus, load_ACC, PC_bus,
load_PC, load_IR, load_MAR,
MDR_bus, load_MDR, ALU_ACC,
ALU_add, ALU_sub, INC_PC,
Addr_bus, CS, R_NW);
endinterfacesiendo el «package» importado el siguiente
package cpu_defs;
parameter WORD_W = 8;
parameter OP_W = 3;
enum logic[2:0] {LOAD=3'b101,
STORE=3'b110,
ADD=3'b010,
SUB=3'b011,
BNE=3'b111} opcodes;
endpackageComo puede observarse nuestro microprocesador es de 8 bits y tenemos tan solo 3 bits para codificar instrucciones. Vamos a definir únicamente 5 instrucciones.
Top de la jerarquía
Ahora la descripción del top de la jerarquía se ve altamente simplificada. Es sin duda una de las cosas que se pretendía con el uso de la construcción «interface»
import cpu_defs::*;
module CPU (input logic CLOCK_50,
input logic [0:0] KEY,
inout wire [WORD_W-1:0] sysbus);
logic clock;
assign clock=CLOCK_50;
logic n_reset;
assign n_reset=KEY[0];
CPU_bus bus (.*);
sequencer s1 (.*);
IR i1 (.*);
PC p1 (.*);
ALU a1 (.*);
RAM r1 (.*);
endmoduleElementos del data-path
Veamos los elementos del data path
PC
import cpu_defs::*;
module PC (CPU_bus.PC_port bus);
logic [WORD_W-OP_W-1:0] count;
assign bus.sysbus = bus.PC_bus ?{{OP_W{1'b0}},count} : 'z;
always_ff @(posedge bus.clock, negedge bus.n_reset)
begin
if (!bus.n_reset)
count <= 0;
else
if (bus.load_PC)
if (bus.INC_PC)
count <= count + 1;
else
count <= bus.sysbus;
end
endmoduleALU
import cpu_defs::*;
module ALU (CPU_bus.ALU_port bus);
logic [WORD_W-1:0] acc;
assign bus.sysbus = bus.ACC_bus ? acc : 'Z;
assign bus.z_flag = acc == 0 ? '1 : '0;
always_ff @(posedge bus.clock, negedge bus.n_reset)
begin
if (!bus.n_reset)
acc <= 0;
else
if (bus.load_ACC)
if (bus.ALU_ACC)
begin
if (bus.ALU_add)
acc <= acc + bus.sysbus;
else if (bus.ALU_sub)
acc <= acc - bus.sysbus;
end
else
acc <= bus.sysbus;
end
endmoduleEste modulo implementa más bien un registro A(o ACC) y un sumador/restador conectados en modo acumulación, de forma que se puedan hacer operaciones de sumas y restas sin problemas.
IR
import cpu_defs::*;
module IR (CPU_bus.IR_port bus);
logic [WORD_W-1:0] instr_reg;
assign bus.sysbus = bus.Addr_bus ?{{OP_W{1'b0}}, instr_reg[WORD_W-OP_W-1:0]}:'z;
always_comb
bus.op = instr_reg[WORD_W-1:WORD_W-OP_W];
always_ff @(posedge bus.clock, negedge bus.n_reset)
begin
if (!bus.n_reset)
instr_reg <= 0;
else
if (bus.load_IR)
instr_reg <= bus.sysbus;
end
endmoduleRAM
import cpu_defs::*;
module RAM (CPU_bus.RAM_port bus);
logic [WORD_W-1:0] mdr;
logic [WORD_W-1:0] salida_mem;
logic [WORD_W-OP_W-1:0] mar;
assign bus.sysbus = bus.MDR_bus ? salida_mem : 'z;
always_ff @(posedge bus.clock, negedge bus.n_reset)
begin
if (!bus.n_reset)
begin
mar <='0;
mdr<='0;
end
else
if (bus.load_MAR)
mar <= bus.sysbus[WORD_W-OP_W-1:0];
else if (bus.load_MDR)
mdr <= bus.sysbus;
end
logic [WORD_W-1:0] mem [0:(1<<(WORD_W-OP_W))-1];
initial
$readmemb("PGM_best.TXT",mem,0);
always_ff @(posedge bus.clock)
begin
if (bus.CS)
if (!bus.R_NW)
mem[mar] <= mdr;
else
salida_mem= mem[mar];
end
endmoduleEste último componente si que tiene algunas características que conviene resaltar:
- Tenemos una memoria «single port» de lectura síncrona
- Tenemos dos registros de apoyo, uno (mdr) que registra el dato de entrada y otro (mar) que registra la dirección.
Control-path
El control path podemos observarlo en el siguiente fichero
import cpu_defs::*;
module sequencer (CPU_bus.seq_port bus);
enum {s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10} state;
always_ff @(posedge bus.clock, negedge bus.n_reset)
begin: seq
if (!bus.n_reset)
state <= s0;
else
case (state)
s0: state <= s1;
s1: state <= s2;
s2: state <= s3;
s3: if (bus.op == STORE)
state <= s4;
else
state <= s6;
s4: state <= s5;
s5: state <= s0;
s6: if (bus.op == LOAD)
state <= s7;
else if (bus.op == BNE)
if (~bus.z_flag)
state <= s9;
else
state <= s10;
else
state <= s8;
s7: state <= s0;
s8: state <= s0;
s9: state <= s0;
s10: state <= s0;
endcase
end
always_comb
begin: com
// reset all the control signals to default
bus.ACC_bus = '0;
bus.load_ACC = '0;
bus.PC_bus = '0;
bus.load_PC = '0;
bus.load_IR = '0;
bus.load_MAR = '0;
bus.MDR_bus = '0;
bus.load_MDR = '0;
bus.ALU_ACC = '0;
bus.ALU_add = '0;
bus.ALU_sub = '0;
bus.INC_PC = '0;
bus.Addr_bus = '0;
bus.CS = '0;
bus.R_NW = '0;
case (state)
s0: begin bus.PC_bus = '1;
bus.load_MAR = '1;
bus.INC_PC = '1;
bus.load_PC = '1;
end
s1: begin
bus.CS = '1;
bus.R_NW = '1;
end
s2: begin
bus.MDR_bus = '1;
bus.load_IR = '1;
end
s3: begin
bus.Addr_bus = '1;
bus.load_MAR = '1;
end
s4: begin
bus.ACC_bus = '1;
bus.load_MDR = '1;
end
s5: begin
bus.CS = '1;
end
s6: begin
bus.CS = '1;
bus.R_NW = '1;
end
s7: begin
bus.MDR_bus = '1;
bus.load_ACC = '1;
end
s8: begin
bus.MDR_bus = '1;
bus.ALU_ACC = '1;
bus.load_ACC = '1;
if (bus.op == ADD)
bus.ALU_add = '1;
else if (bus.op == SUB)
bus.ALU_sub = '1;
end
s9: begin
bus.MDR_bus = '1;
bus.load_PC = '1;
end
s10: ;
endcase
end
endmoduleComo puede observarse es una máquina Mealy de 11 estados
Procedimiento de verificación
Supongamos que tenemos un programa preparado para comprobar todas las instrucciones desarrolladas. En nuestro caso es un programa para generar números fibonacci.
10110010 //load A,[18]; Carga en el acumulador el número i-ésimo.
01010011 //add A,[19]; Súmale el (i+1)-ésimo.
11010100 //store [20],A; Guardalo en la siguiente posición.
10100000 //load A,[0]; Coje la primera instrucción.
01010010 //add A,[18]; Súmale 1, (Fib(0)=1). De este modo aumenta en uno la dirección.
11000000 //store [0],A; Guarda la nueva instrucción.
10100001 //load A,[1]; idem...
01010010 //add A,[18];
11000001 //store [1],A;
10100010 //load A,[2]; idem...
01010010 //add A,[18];
11000010 //store [2],A;
10101111 //load A,[15]; idem...
01110010 //sub A,[18];
11001111 //store [15],A;
00001011 //cantidad de numeros fibonacci
11110001 //jump 0; Ejecuta las nuevas instrucciones.
00000000
00000001 //Fib(0)
00000001 //Fib(1)Vamos a explicar con el siguiente vídeo cómo verificar el funcionamiento del microprocesador desarrollado bajo una plataforma FPGA remota.


