verilog de rama de ciclo único de mips

Soy bastante nuevo en Verilog, diseño de hardware y arquitectura de computadoras. Sin embargo, he intentado diseñar un procesador MIPS simplificado. Parece funcionar bien en su mayoría, pero cada vez que lo simulo, se cuelga de una instrucción BEQ.

Estoy tratando de hacer que ejecute el siguiente programa (R1 está precargado con 32'd10 y R4 está precargado con 32'd1):

0    ADD R0 R1 R5; // R1 = 10
4    ADD R0 R1 R5; // R5 = 10 (redundant)
8    ADD R5 R6 R6; // R6 = R5 + R6
C    SUB R5 R4 R5; // R5 = R5 - 1
10   SLT R4 R5 R7; // R4 < R5
14   BEQ R7 R4 -3; // Loop back to 8 if R5 > 1 

Aquí está la salida de GTKWave:

ingrese la descripción de la imagen aquí

Según tengo entendido, los 16 bits inferiores de la instrucción deben ir a sign_extend, donde se concatena con 16 copias del bit de signo. Esto luego va a shift_left2, donde la dirección se multiplica por 4. Esto se suma a la dirección anterior + 4.

Los dos operandos enviados a la ALU son iguales, por lo que se afirma la señal z0. La señal de bifurcación es afirmada por el módulo de control, en base a los bits [31:26] de la instrucción. La rama y el cero tienen AND en branch_and, lo que hace que branchnd se afirme. Esto se alimenta a un mux para seleccionar la dirección de rama en lugar de la dirección anterior incrementada y se retroalimenta al contador del programa, se asigna a next_address en el siguiente ciclo de reloj y se alimenta a la memoria de instrucciones para obtener la siguiente dirección.

Parece que todo esto sucede en mi simulación. La dirección bifurcada se puede ver en el complemento a los 12 segundos en la imagen de arriba, por lo que parece funcionar correctamente hasta ese momento. Sin embargo, no puedo entender por qué se cuelga. Seguramente esto debería introducirse en el contador del programa, asignarse a next_address en el siguiente posedge del reloj y utilizarse para obtener la instrucción. Si cambio el programa para que no se tome la rama, continúa ejecutándose hasta que se queda sin instrucciones, por lo que parece estar relacionado con tomar la rama.

Intenté cambiar las listas de sensibilidad, agregar/eliminar el reloj de los módulos y casi cualquier otra cosa que se me ocurra. Tal vez me estoy perdiendo algo obvio. Agradecería mucho si alguien pudiera ayudar. Soy nuevo aquí, así que si esta no es la forma correcta de hacer una pregunta o si hay algo que pueda hacer para que esta pregunta sea más fácil de responder, házmelo saber.

Gracias

module MIPS_tb();

wire [4:0] rs1, rt1, rd1, writereg1;
wire [31:0] ins, register0, register1, register4, register5, register6, register7, addin, aluout, nextadd,
        op1, op2, rfoutput2, dmreaddata, se_address, wdata, braddr, shaddr, incPCaddr;
wire reg_write, branch, branchnd, z0, regds, memtoreg, memwrite, alusrc;
reg clock;
reg reset;


MIPS mips1(clock, reset, rs1, rt1, rd1, writereg1, reg_write, ins,
     register1, register4, register5, register6, register7, branch, branchnd, z0, addin, aluout, nextadd,
    op1, op2, rfoutput2, dmreaddata, se_address, wdata, braddr, shaddr, incPCaddr, regds, memtoreg, memwrite, alusrc);

initial
begin
    $dumpfile("MIPS_MIPS_tb.vcd");
    $dumpvars(0, clock, reset, rs1, rt1, rd1, writereg1, reg_write, ins,
     register1, register4, register5, register6, register7, branch, branchnd, z0, addin, aluout, nextadd,
    op1, op2, rfoutput2, dmreaddata, se_address, wdata, braddr, shaddr, incPCaddr, regds, memtoreg, memwrite, alusrc);
    reset = 1'b1;
    clock = 1'b0;
#1
    reset = 1'b0;

    repeat(250)
#1      
        clock = !clock;
#1
    $finish;
end


endmodule   

MIPS

module MIPS(clock, reset, rs1, rt1, rd1, writereg1, reg_write, ins,
     register1, register4, register5, register6, register7, branch, branchnd, z0, addin, aluout, nextadd,
    op1, op2, rfoutput2, dmreaddata, se_address, wdata, braddr, shaddr, incPCaddr, regds, memtoreg, memwrite, alusrc);

input clock, reset;


output [4:0] rs1, rt1, rd1, writereg1;
output [31:0] ins, addin, aluout;
output reg_write;
output [31:0] register1, register4, register5, register6, register7, nextadd, op1, op2,
        rfoutput2, dmreaddata, se_address, wdata, braddr, shaddr, incPCaddr;
output branch, branchnd, z0, regds, memtoreg, memwrite, alusrc;


wire [31:0] instruction, next_address, address_in, operand1, operand2, reg_file_output2, 
        ALU_result, data_mem_read_data, sign_extended_address, write_data,
        branch_address, shifted_address, incremented_PC_address;
wire RegDst, Branch, MemtoReg, MemWrite, ALUSrc, RegWrite, zero, BEQ;
wire [1:0] ALUOp;
wire [4:0] rs, rt, rd, write_register;
wire [5:0] funct;
wire [15:0] addressop;
wire [3:0] ALU_control_vector;

assign rs1 = rs;
assign rt1 = rt;
assign rd1 = rd;
assign writereg1 = write_register;
assign reg_write = RegWrite;
assign ins = instruction;
assign z0 = zero;
assign branch = Branch;
assign branchnd = BEQ;
assign addin = address_in;
assign aluout = ALU_result;
assign nextadd = next_address;

assign op1 = operand1;
assign op2 = operand2;
assign rfoutput2 = reg_file_output2;
assign dmreaddata = data_mem_read_data;
assign se_address = sign_extended_address;
assign wdata = write_data;
assign braddr = branch_address;
assign shaddr = shifted_address;
assign incPCaddr = incremented_PC_address;
assign regds = RegDst;
assign memtoreg = MemtoReg;
assign memwrite = MemWrite;
assign alusrc = ALUSrc; 


program_counter     pc1(next_address, address_in, clock, reset);

register_file   rf1(operand1, reg_file_output2, rs, rt, write_register, write_data, RegWrite,
             register1, register4, register5, register6, register7);

ALU alu1(ALU_result, zero, operand1, operand2, ALU_control_vector);

ALU_control aluctrl1(ALU_control_vector, ALUOp, funct);

data_memory dm1(data_mem_read_data, reg_file_output2, ALU_result, MemWrite);

sign_extend se1(sign_extended_address, addressop);

instruction_memory  im1(instruction, next_address);

control     ctrl1(RegDst, Branch, MemtoReg, ALUOp, MemWrite, ALUSrc, RegWrite, instruction,
            rs, rt, rd, funct, addressop);  

branch_adder    ba1(branch_address, shifted_address, incremented_PC_address);

PC_increment    pci(incremented_PC_address, next_address);

branch_and  brand(BEQ, Branch, zero);

shift_left2 shl2(shifted_address, sign_extended_address);

mux_2to1    branch_mux(address_in, BEQ, incremented_PC_address, branch_address);

mux_2to1_5bit   write_reg_mux(write_register, RegDst, rt, rd);

mux_2to1    write_data_mux(write_data, MemtoReg, ALU_result, data_mem_read_data);

mux_2to1    ALU_source_mux(operand2, ALUSrc, reg_file_output2, sign_extended_address);

endmodule

Unidad de control

module control(RegDst, Branch, MemtoReg, ALUOp, MemWrite, ALUSrc, RegWrite, instruction,
    rs, rt, rd, funct, addressop);  

output RegDst, Branch, MemtoReg, MemWrite, ALUSrc, RegWrite;
output [4:0] rs, rt, rd;
output [5:0] funct;
output [15:0] addressop;
output [1:0] ALUOp;
input [31:0] instruction;

wire [5:0] Op = instruction[31:26];

assign rs = instruction[25:21];
assign rt = instruction[20:16];
assign rd = instruction[15:11];
assign funct = instruction[5:0];
assign addressop = instruction[15:0];

reg [7:0] Control;

assign RegDst = Control[7];
assign RegWrite = Control[6];
assign ALUSrc = Control[5];
assign MemWrite = Control[4];
assign MemtoReg = Control[3];
assign Branch = Control[2];
assign ALUOp = Control[1:0];

initial
    Control = 7'd0;

always @(*)
    casex(Op)
        32'd0 : Control = 8'b11000010; // R-TYPE

        32'd35 : Control = 8'b01101000; // LW

        32'd43 : Control = 8'bx011x000; // SW

        32'd4 : Control = 8'bx000x101; // BEQ

        default : Control = 8'b00000000; // NOP  
    endcase

endmodule       

Memoria de instrucciones

module instruction_memory(instruction, address);

output reg [31:0] instruction;
input [31:0] address;

reg [31:0] prog [40:0];

initial
begin
    prog[0] <= 32'b000000_00000_00001_00101_00000000000;
    prog[4] <= 32'b000000_00000_00001_00101_00000000000;
    prog[8] <= 32'b000000_00000_00000_00110_00000000000;
    prog[12] <= 32'b000000_00101_00110_00110_00000000000;
    prog[16] <= 32'b000000_00101_00100_00101_00000000010;
    prog[20] <= 32'b000000_00100_00101_00111_00000001010;
    prog[24] <= 32'b000100_00111_00100_11111_11111111100; 
    prog[28] <= 32'b000000_00101_00100_00101_00000000010;
    prog[32] <= 32'b000000_00101_00100_00111_00000001010;       

    prog[36] <= 32'b000000_00100_00110_00110_00000000000;
    prog[40] <= 32'b000000_00100_00110_00110_00000000000;
end

always @(address)
    instruction = prog[address];

endmodule

firmar extender

module sign_extend(sign_extended_address, instruction_addr);

output reg [31:0] sign_extended_address;
input [15:0] instruction_addr;

always @(*)
begin
    sign_extended_address[15:0] = instruction_addr;
    if(instruction_addr[15]==1'b1)
        sign_extended_address[31:16] = 16'b1111_1111_1111_1111;
    else
        sign_extended_address[31:16] = 16'b0000_0000_0000_0000;
end


endmodule

Unidad de control ALU

module ALU_control(ALU_control_vector, ALUOp, funct);

output reg [3:0] ALU_control_vector;
input [1:0] ALUOp;
input [5:0] funct;

initial
    ALU_control_vector = 4'b0000;

always @(*)
begin
    case(ALUOp[1])
        1'b0 :  case(ALUOp[0])
                1'b0 : ALU_control_vector = 4'b0010; // ADD for LW, SW

                1'b1 : ALU_control_vector = 4'b0110; // SUB for BEQ
            endcase

        1'b1 :  case(funct[3:0])
                4'b0000 : ALU_control_vector = 4'b0010; // ADD

                4'b0100 : ALU_control_vector = 4'b0000; // AND

                4'b0101 : ALU_control_vector = 4'b0001; // OR

                4'b0010 : ALU_control_vector = 4'b0110; // SUB

                4'b1010 : ALU_control_vector = 4'b0111; // SLT

                default : ALU_control_vector = 4'b0010; // ADD (doesn't matter)
            endcase
    endcase
end

endmodule

ALU

module ALU(ALU_result, zero, operand1, operand2, ALU_control_vector);

output reg [31:0] ALU_result;
output reg zero;
input [31:0] operand1, operand2;
input [3:0] ALU_control_vector;

initial
begin
    ALU_result = 32'd0;
    zero = 0;
end

always @(*)
begin
    case(ALU_control_vector)
        4'b0000 : ALU_result = operand1 & operand2;

        4'b0001 : ALU_result = operand1 | operand2;

        4'b0010 : ALU_result = operand1 + operand2;

        4'b0110 : ALU_result = operand1 - operand2;

        4'b0111 : ALU_result = (operand1 < operand2) ? 32'd1 : 32'd0;
    endcase
    if (ALU_result == 0)
        zero = 1'b1;
    else
        zero = 1'b0;
end

endmodule

Registrar archivo

module register_file(operand1, reg_file_output2, rs, rt, write_register, write_data, RegWrite,
         register1, register4, register5, register6, register7);

output reg [31:0] operand1, reg_file_output2;
output [31:0] register0, register1, register4, register5, register6, register7;
input [31:0] write_data;
input [4:0] rs, rt, write_register;
input RegWrite;

reg [31:0] registers [31:1];  // Register 0 reserved for "0"

assign register1 = registers[1];
assign register4 = registers[4];
assign register5 = registers[5];
assign register6 = registers[6];
assign register7 = registers[7];

initial
begin
    registers[1] = 10;
    registers[4] = 1;
    registers[5] = 0;
    registers[6] = 0;
    registers[7] = 0;
    operand1 = 0;
    reg_file_output2 = 0;
end

always @(*)
begin
    operand1 = (rs == 0) ? 32'd0 : registers[rs];
    reg_file_output2 = (rt == 0) ? 32'd0 : registers[rt];
    if(RegWrite)
        registers[write_register] = write_data;
end

endmodule

Contador de programa

module program_counter(next_address, address, clock, reset);

output [31:0]  next_address;
input [31:0] address;
input clock, reset;

reg [31:0] pc_next;

assign next_address = pc_next;


always @(posedge clock, posedge reset)
begin
if(reset)
    pc_next = 32'd0;
else
    pc_next = address;
end

endmodule

Sumador de ramas

module branch_adder(branch_address, shifted_addr_instruction, incremented_PC_addr);

output [31:0] branch_address;
input [31:0] shifted_addr_instruction, incremented_PC_addr;

assign branch_address = shifted_addr_instruction + incremented_PC_addr;

endmodule

Mux 1

module mux_2to1(out, select, a, b);

output reg [31:0] out;
input select;
input [31:0] a, b;

initial
    out = 0;

always @(*)
begin
    if(select)
        out = b;
    else
        out = a;
end

endmodule

Mux 2

module mux_2to1_5bit(out, select, a, b);

output reg [4:0] out;
input select;
input [4:0] a, b;

initial
    out = 0;

always @(*)
begin
    if(select)
        out = b;
    else
        out = a;
end

endmodule

Incrementador de PC

module PC_increment(incremented_PC_address, current_address);

output [31:0] incremented_PC_address;
input [31:0] current_address;

assign incremented_PC_address = current_address + 4; 

endmodule

Rama/Cero Y

module branch_and(BEQ, branch, zero);

output BEQ;
input branch, zero;

and(BEQ, branch, zero);

endmodule

Dirección de desplazamiento a la izquierda

module shift_left2(shifted_address, sign_extended_address);

output [31:0] shifted_address;
input [31:0] sign_extended_address;

assign shifted_address = sign_extended_address << 2;

endmodule

Memoria de datos

module data_memory(data_mem_read_data, data_mem_write_data, ALU_result, MemWrite);

output reg [31:0] data_mem_read_data;
input [31:0] ALU_result, data_mem_write_data;
input MemWrite;

wire MemRead = ~MemWrite;

reg [31:0] data_registers [255:0];

initial
    data_mem_read_data = 0;

always @(ALU_result, data_mem_write_data)
begin
    if(MemWrite)
        data_registers[ALU_result] = data_mem_write_data;
    if(MemRead)
        data_mem_read_data = data_registers[ALU_result];
end

endmodule
Edite su pregunta para explicar, en palabras, cómo cree que se supone que debe funcionar la ramificación en su diseño. En particular, incluya cómo ha implementado la ranura de retraso de rama MIPS.
@ChrisStratton: he editado para incluir una explicación de cómo espero que funcione la ramificación. Debería haber mencionado que se supone que es un procesador de ciclo único. Tengo entendido que no necesito preocuparme por el retraso de la sucursal en este caso, pero tal vez me equivoque. Como digo, soy bastante nuevo en esto, así que en realidad no sé qué es una ranura de retardo de bifurcación, solo parece que se menciona como necesaria para un procesador segmentado.
No sé dónde está tu error, pero tengo algunos consejos generales de verilog para ti. 1. Nunca use la posición basada en listas de puertos. Utilice siempre el nombre basado. Terminará agregando más puertos y estropeando todo. 2. Tienes muchos más módulos de los que necesitas. Un montón de sus módulos solo para una línea de lógica combinacional, así que hágalo en línea. 3. Puedes simplificar mucha lógica. Tiene una declaración de caso que verifica si una señal es todo 0 o no, y le asigna una salida. Podrías hacer fácilmente "assign foo = (bar!=0);".
Y si su simulación se está estancando, hay un bucle combinacional en alguna parte. Equivalente de a = ~a;.
"MIPS" en combinación con "rama de ciclo único" es un oxímoron . Como mínimo, al no implementar una ranura de retraso de bifurcación, ha roto la compatibilidad binaria. Incluso si su diseño finalmente funciona, estará limitado al ensamblaje manual, ya que no podrá usar compiladores que asuman esto.
@ChrisStratton: no tengo intención de usar el diseño para otra cosa que no sea una herramienta educativa. Una vez que esto funcione, puedo comenzar a analizar la canalización, la implementación del resto del conjunto de instrucciones MIPS, el retraso de bifurcación, la adición de cachés, etc. Como digo, soy bastante nuevo en esto, así que ni siquiera estoy mirando más allá. asamblea por el momento.

Respuestas (2)

En cualquier bloque secuencial siempre, debe usar una asignación sin bloqueo (el signo menor que/igual, <=). Si no es la causa de este problema, seguramente causará una capa.

¡Funciona! Parece que cambiar a asignaciones sin bloqueo y también hacer que el archivo de registro emita cables y asignar continuamente desde los registros seleccionados solucionó el problema. Muchas gracias

La regla es simple: si un proceso escribe en una variable sincronizada con un evento y otro proceso lee la misma variable sincronizada con el mismo evento, debe escribir usando un NBA asegurándose de que el proceso de lectura use el valor anterior de la variable. Si, en cambio, usa una asignación de bloqueo en esta situación, existe una condición de carrera entre obtener el valor nuevo o antiguo de la variable porque el orden de ejecución entre los procesos de lectura y escritura es indeterminado.

Por "evento" me refiero a un borde de un reloj o habilitar. Y por "igual" me refiero a la expresión idéntica o combinada de la misma señal.