`define MEMBASE 'h400000 `define DATABASE 'h10010000 `define TEXTSIZE 'h100 `define MEMSIZE 'h200 `define MEMRANGE `MEMBASE:`MEMBASE+`MEMSIZE-1 `define A(addr) ((addr)-`DATABASE+`MEMBASE+`TEXTSIZE) `define MEM(addr) mem[((addr)-`DATABASE+`MEMBASE+`TEXTSIZE)] `ifdef CYCLE_LIMIT `else `define CYCLE_LIMIT 1000 `endif `ifdef WATCH_LIMIT `else `define WATCH_LIMIT 500 `endif `ifdef PTRACE_LIMIT `else `define PTRACE_LIMIT 10000 `endif module test_proc(); parameter shadow_trace = 1; parameter tr_size = 100; parameter cycle_delta = 2; // Cycle count for first cpu cycle after reset. // Exception Codes // parameter EXC_none = 8'd0; parameter EXC_if_bus = 8'd1; parameter EXC_if_seg = 8'd2; parameter EXC_id_ins = 8'd3; // Illegal Instruction parameter EXC_id_opr = 8'd4; // Illegal Operand parameter EXC_id_sys = 8'd5; parameter EXC_me_bus = 8'd6; parameter EXC_me_seg = 8'd7; wire [7:0] exception; reg reset, clk, clk2; wire exc; system_p1 dut(exception,reset,clk); tb_proc p2(exc,clk2); reg [31:0] gpr_shadow [0:31]; // DUT reg [31:0] gpr_shadow2 [0:31]; reg [31:0] fpr_shadow [0:31]; reg [31:0] fpr_shadow2 [0:31]; reg [31:0] fpr_expecting_wb [0:31]; integer fpr_expecting_wb_cnt [0:31]; reg [319:0] isource[dut.m1.text_base>>2:dut.m1.text_base+dut.m1.text_size>>2]; reg [31:0] regname [0:31]; reg [19*8-1:0] excname [0:10]; integer i, reg_old, reg_new, reg_sim; real freg_old, freg_new; reg go; real cycle_count; integer ptrace_count; integer icount; integer changed_reg_count; integer changed_freg, changed_freg_count; reg [31:0] freg_wb_pc; reg [31:0] tr_pc_a [0:tr_size]; reg [31:0] tr_regv_a [0:tr_size]; reg [4:0] tr_regno_a [0:tr_size]; reg [31:0] tr_pc; reg [31:0] tr_regv; reg [4:0] tr_regno; reg [31:0] dut_pc; integer tr_fd, tr_icount_end; integer pt_fd; always wait( go ) begin clk = 0; #8; clk = 1; #2; end function [31:0] dtof; input [63:0] d; begin dtof[31] = d[63]; dtof[30:23] = d[62:52] - 1023 + 127; dtof[22:0] = d[51:29]; end endfunction function [63:0] ftod; input [31:0] f; begin ftod[63] = f[31]; ftod[62:52] = f[30:23] - 127 + 1023; ftod[51:0] = {f[22:0],29'd0}; end endfunction function real ftor; input [31:0] f; ftor = $bitstoreal(ftod(f)); endfunction function [31:0] rtof; input r; real r; rtof = dtof($realtobits(r)); endfunction function [4:0] ito5; input i; integer i; ito5 = i; endfunction function real itor; input i; integer i; itor = i; endfunction task initmem; input [31:0] addr; input [31:0] text; input [319:0] source; reg [2:0] err; begin err = dut.m1.tbi_poke_word(addr,text); if( err ) begin $display("Error %d initializing text address 0x%h.", err, addr); $stop; end {p2.mem[addr],p2.mem[addr+1],p2.mem[addr+2],p2.mem[addr+3]} = text; while( source[319:312] === 8'b0 ) source = {source[311:0]," "}; isource[addr>>2] = source; end endtask task initdmem; input [31:0] addr; input [31:0] word; reg [31:0] daddr; reg [2:0] err; begin err = dut.m1.tbi_poke_word(addr,word); if( err ) begin $display("*** Error %d initializing data address 0x%h. ***", err, addr); $stop; end daddr = `A(addr); {p2.mem[daddr],p2.mem[daddr+1],p2.mem[daddr+2],p2.mem[daddr+3]} = word; end endtask task get_trace_record; output [31:0] tr_pc; output [5:0] tr_regno; output [31:0] tr_regv; integer i; reg [31:0] cpy_reg [0:31]; if ( shadow_trace ) begin tr_pc = p2.pc; p2.step; tr_regno = 0; tr_regv = 0; for(i=0; i<32; i=i+1) if( gpr_shadow2[i] != p2.gpr[i] ) begin tr_regno = i; tr_regv = p2.gpr[i]; gpr_shadow2[i] = tr_regv; end `ifdef FP_PROC for(i=0; i<32; i=i+1) if( fpr_shadow2[i] !== p2.fpr[i] ) begin fpr_expecting_wb[i] = tr_pc; fpr_expecting_wb_cnt[i] = fpr_expecting_wb_cnt[i] + 1; fpr_shadow2[i] = p2.fpr[i]; end `endif end endtask task write_segments; begin:PTRR reg valid; reg [15:0] name; reg [31:0] pc; reg [31:0] din; reg [7:0] exc; reg occ; real fdin; if( ptrace_count < `PTRACE_LIMIT ) forever begin dut.cpu1.tbi_iterate_pipeline_segments(valid,name,pc,din,exc,occ); if( !valid ) disable PTRR; ptrace_count = ptrace_count + 1; if( ptrace_count == `PTRACE_LIMIT ) $display("*** Pipeline tracing stopping after cycle %.0f. ***", cycle_count); // Modelsim bug workaround: Modelsim formats integers with // about 8 characters of space regardless of format specifier. fdin = din; $fwrite(pt_fd,"%2s %.0f %d %d", name, fdin, occ, exc != 0); if( name == "IF" ) $fwrite(pt_fd," 0x%h %-s\n", pc, isource[pc>>2]); else $fwrite(pt_fd,"\n"); end end endtask integer rno; task initregs; input [31:0] name; input [3:0] cnt; integer i; for(i=0; i<cnt; i=i+1) begin regname[rno] = name + i; rno = rno + 1; end endtask task cpi_and_stop; real cpi; integer i; begin $fclose(pt_fd); `ifdef FP_PROC for(i=0; i<32; i=i+1) begin if( fpr_expecting_wb[i] ) $display("FAIL: Expecting wb to f%d: %f (c) =? %f by 0x%h", ito5(i),ftor(fpr_shadow2[i]),ftor(dut.cpu1.fpr[i]), fpr_expecting_wb[i]); end `endif $display(""); $display("-----------------------------------------------------------------------------"); $display("Pipeline execution diagram of last few cycles:\n"); $system("ped end"); $display("-----------------------------------------------------------------------------"); cpi = icount ? cycle_count / icount : 0; $display("Executed %d instructions at %.2f CPI.", icount, cpi); $display("\nEnd of Testbench Run\n"); $stop; end endtask initial begin go = 0; cycle_count = -cycle_delta; icount = 0; reset = 1; ptrace_count = 0; begin:INITNAMES integer i; regname[0] = "zero"; regname[1] = "at"; rno = 2; initregs("v0",2); initregs("a0",4); initregs("t0",8); initregs("s0",8); initregs("t8",2); initregs("k0",2); regname[28] = "gp"; regname[29] = "sp"; regname[30] = "fp"; regname[31] = "ra"; excname[EXC_none] = "None"; excname[EXC_if_bus] = "PC Bus"; excname[EXC_if_seg] = "PC Segmentation"; excname[EXC_id_ins] = "Illegal Instruction"; excname[EXC_id_opr] = "Illegal Operand"; excname[EXC_id_sys] = "System Call"; excname[EXC_me_bus] = "L/S Bus"; excname[EXC_me_seg] = "L/S Segmentation"; end pt_fd = $fopen("pipetrace.txt"); `include `MIPS_PROG for(i=0; i<32; i=i+1) begin:B reg [31:0] val, fval; val = i * 10; fval = rtof(i * 100.0 + i/10.0); dut.cpu1.tbi_poke_gpr(i,val); `ifdef FP_PROC dut.cpu1.fpr[i] = fval; `endif p2.gpr[i] = val; p2.fpr[i] = fval; gpr_shadow[i] = val; gpr_shadow2[i] = val; fpr_shadow[i] = fval; fpr_shadow2[i] = fval; fpr_expecting_wb[i] = 0; fpr_expecting_wb_cnt[i] = 0; end p2.pc = 'h400000; p2.npc = p2.pc + 4; icount = 0; go = 1; changed_freg = 0; changed_reg_count = 0; changed_freg_count = 0; wait( clk == 0 ); @( posedge clk ); @( negedge clk ); #1; reset = 0; $display("============================================================================="); $display("Pipelined Processor Testbench"); $display("\nMIPS Program %s",`MIPS_PROG); $display("Cycle Limit %d. (See CYCLE_LIMIT)",`CYCLE_LIMIT); $display("Watch Limit %d. (See WATCH_LIMIT)",`WATCH_LIMIT); $display("Pipeline Trace Limit %d. (See PTRACE_LIMIT)",`PTRACE_LIMIT); $display("-----------------------------------------------------------------------------"); fork:MAIN forever begin cycle_count = cycle_count + 1; if( cycle_count > `CYCLE_LIMIT ) begin $display("\n*** Cycle count limit reached. ***"); cpi_and_stop; end if( cycle_count == `WATCH_LIMIT ) $display("\n*** Watch limit reached, registers and instruction printing stopped. ***"); write_segments; if( dut.cpu1.tbi_inst_done === 1 ) begin dut_pc = dut.cpu1.tbi_done_pc; if( cycle_count < `WATCH_LIMIT ) $display(" PC 0x%h: %-s", dut_pc, isource[dut_pc>>2] ); if( shadow_trace ) begin get_trace_record(tr_pc,tr_regno,tr_regv); if( tr_pc !== dut_pc ) begin $display("PC mismatch at instruction %d, 0x%h (correct) != %h", icount, tr_pc, dut_pc ); cpi_and_stop; end for(i=0; i<32; i=i+1) begin reg_old = gpr_shadow[i]; reg_new = dut.cpu1.tbi_peek_gpr(i); reg_sim = gpr_shadow2[i]; if( reg_old !== reg_new ) begin if( cycle_count < `WATCH_LIMIT ) $display(" Register $%2.0f (%s): 0x%h (%d) -> 0x%h (%d)", itor(i), regname[i], reg_old, reg_old, reg_new, reg_new); gpr_shadow[i] = reg_new; end if( reg_sim !== reg_new ) begin $display("FAIL: Wrong value in %d 0x%h (correct) != 0x%h", ito5(i), reg_sim, reg_new); cpi_and_stop; end end end icount = icount + 1; end if( exception ) disable MAIN; @( negedge clk ); // Memory is clocked on negative edge, give simulator time // for combinational logic driven my memory outputs. #1; end // Watch FP regs `ifdef FP_PROC while( !exception ) begin if( shadow_trace ) begin #1; // Wait for loop above to step functional simulator. if( changed_freg_count ) begin if( !fpr_expecting_wb[changed_freg] ) begin $display("FAIL: Did not expect a writeback to f%d.", ito5(changed_freg)); cpi_and_stop; end if( fpr_shadow2[changed_freg] !== fpr_shadow[changed_freg] && fpr_expecting_wb_cnt[changed_freg] < 2 ) begin $display("FAIL: Wrong value written to f%d: %f (c) != %f", ito5(changed_freg), ftor(fpr_shadow2[changed_freg]), freg_new); cpi_and_stop; end if( fpr_expecting_wb[changed_freg] == freg_wb_pc ) begin fpr_expecting_wb_cnt[changed_freg] = 0; fpr_expecting_wb[changed_freg] = 0; end else if( fpr_expecting_wb_cnt[changed_freg] == 1 ) begin $display("FAIL: Wrong instruction writing f%d, 0x%h(c) != 0x%h", ito5(changed_freg), fpr_expecting_wb[changed_freg], freg_wb_pc); cpi_and_stop; end else begin fpr_expecting_wb_cnt[changed_freg] = fpr_expecting_wb_cnt[changed_freg] - 1; if( cycle_count < `WATCH_LIMIT ) $display("f%d sort of okay %d",changed_freg, fpr_expecting_wb_cnt[changed_freg]); end end changed_freg_count = 0; changed_freg = 0; end freg_wb_pc = dut.cpu1.wb_fp_pc; @( negedge clk ); // Memory is clocked on negative edge, give simulator time // for combinational logic driven my memory outputs. #1; for(i=0; i<32; i=i+1) if( fpr_shadow[i] !== dut.cpu1.fpr[i] ) begin freg_old = ftor(fpr_shadow[i]); freg_new = ftor(dut.cpu1.fpr[i]); changed_freg = i; changed_freg_count = changed_freg_count + 1; if( cycle_count < `WATCH_LIMIT ) $display(" Register f%d: %f -> %f", ito5(i), freg_old, freg_new); fpr_shadow[i] = dut.cpu1.fpr[i]; end end `endif join $display(""); $display("-----------------------------------------------------------------------------"); if( exception ) begin if( exception == EXC_id_sys ) begin $display("Ending normally at a syscall instruction."); if( shadow_trace && tr_pc !== dut_pc ) begin $display("PC mismatch at instruction %d, 0x%h (correct) != %h", icount, tr_pc, dut_pc ); end end else $display("%s exception for instruction at address 0x%h", excname[exception], dut.cpu1.tbi_done_pc); end cpi_and_stop; end endmodule module tb_proc(exc,clk); input clk; output exc; reg exc; reg [7:0] mem [`MEMRANGE]; reg [31:0] pc, npc, nnpc, ir; reg [31:0] gpr [0:31]; reg [63:0] fpr[0:31]; reg [5:0] opcode, funct; reg [4:0] rs, rt, rd, sa, fs, ft, fd, fmt; reg [15:0] immed; reg [25:0] ii; reg [31:0] uimm16, simm16; reg [31:0] branch_target; real fs_v, ft_v; // Values for funct field. parameter F_sll = 6'h0; parameter F_srl = 6'h2; parameter F_sllv = 6'h4; parameter F_srlv = 6'h6; parameter F_add = 6'h20; parameter F_sub = 6'h22; parameter F_and = 6'h24; parameter F_or = 6'h25; parameter F_xor = 6'h26; // Values for opcode field. parameter O_rfmt = 6'h0; parameter O_j = 6'h2; parameter O_beq = 6'h4; parameter O_bne = 6'h5; parameter O_addi = 6'h8; parameter O_slti = 6'ha; parameter O_sltiu = 6'hb; parameter O_andi = 6'hc; parameter O_ori = 6'hd; parameter O_lui = 6'hf; parameter O_cop1 = 6'h11; parameter O_lw = 6'h23; parameter O_lbu = 6'h24; parameter O_sw = 6'h2b; parameter O_sb = 6'h28; parameter O_lwc1 = 6'h31; /// Coprocessor 1 (FP) function Field Values // parameter C1_add = 6'h0; parameter C1_sub = 6'h1; parameter C1_mul = 6'h2; initial begin exc = 0; pc = 0; npc = 4; end task step; begin ir = {mem[pc],mem[pc+1],mem[pc+2],mem[pc+3]}; // R Format {opcode,rs,rt,rd,sa,funct} = ir; {fmt,ft,fs,fd} = {rs,rt,rd,sa}; nnpc = npc + 4; // May be reassigned below. // I Format (Also uses opcode, rs, and rt.) immed = ir[15:0]; // J Format (Also uses opcode.) ii = ir[25:0]; uimm16 = { 16'b0, immed }; simm16 = immed[15] ? { 16'hffff, immed } : uimm16; branch_target = npc + ( simm16 << 2 ); case( opcode ) O_rfmt: // // R-Format Instructions case( funct ) F_sllv : gpr[rd] = gpr[rt] << ( gpr[rs] & 32'h1f ); F_srlv : gpr[rd] = gpr[rt] >> ( gpr[rs] & 32'h1f ); F_sll : gpr[rd] = gpr[rt] << sa; F_srl : gpr[rd] = gpr[rt] >> sa; F_add : gpr[rd] = gpr[rs] + gpr[rt]; F_and : gpr[rd] = gpr[rs] & gpr[rt]; F_or : gpr[rd] = gpr[rs] | gpr[rt]; F_xor : gpr[rd] = gpr[rs] ^ gpr[rt]; F_sub : gpr[rd] = gpr[rs] - gpr[rt]; default : exc = 1; endcase O_cop1: begin fs_v = ftor( fpr[fs] ); ft_v = ftor( fpr[ft] ); case( funct ) C1_add: fpr[fd] = rtof(fs_v + ft_v); C1_sub: fpr[fd] = rtof(fs_v - ft_v); C1_mul: fpr[fd] = rtof(fs_v * ft_v); default : exc = 1; endcase end // // I- and J-Format Instructions O_j : nnpc = {npc[31:28],ii,2'b0}; O_beq : if( gpr[rs] == gpr[rt] ) nnpc = branch_target; O_bne : if( gpr[rs] != gpr[rt] ) nnpc = branch_target; O_andi : gpr[rt] = gpr[rs] & uimm16; O_sltiu : gpr[rt] = gpr[rs] < simm16; O_slti : begin:A integer a, b; a = gpr[rs]; b = simm16; gpr[rt] = a < b; end O_addi : gpr[rt] = gpr[rs] + simm16; O_ori : gpr[rt] = gpr[rs] | uimm16; O_lui : gpr[rt] = { immed, 16'b0 }; O_lbu : gpr[rt] = { 24'b0, `MEM( gpr[rs] + simm16 ) }; O_lw : begin:LW reg [31:0] ea; ea = gpr[rs] + simm16; gpr[rt] = {`MEM(ea),`MEM(ea+1),`MEM(ea+2),`MEM(ea+3)}; end O_sw : begin:SW reg [31:0] ea; ea = gpr[rs] + simm16; {`MEM(ea),`MEM(ea+1),`MEM(ea+2),`MEM(ea+3)} = gpr[rt]; end O_lwc1 : begin:LFC1 reg [31:0] ea; ea = gpr[rs] + simm16; fpr[rt] = {`MEM(ea),`MEM(ea+1),`MEM(ea+2),`MEM(ea+3)}; end O_sb : `MEM( gpr[rs] + simm16 ) = gpr[rt]; default : exc = 1; endcase gpr[0] = 0; pc = npc; npc = nnpc; end endtask endmodule