blob: a68a202e41b84d3b1ecd7c5d0cd02cb09cdf23c6 [file] [log] [blame]
/*
* LatticeMico32
* System Test Bench
*
* Copyright (c) 2012 Michael Walle <michael@walle.cc>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
`include "lm32_include.v"
`timescale 1 ns / 1 ps
module testbench();
integer i;
reg sys_rst;
reg sys_clk;
reg [31:0] interrupt;
reg i_ack;
wire [31:0] i_adr;
wire i_cyc;
wire [31:0] i_dat;
wire i_stb;
reg d_ack;
wire [31:0] d_adr;
wire d_cyc;
wire [31:0] d_dat_i;
wire [31:0] d_dat_o;
wire [3:0] d_sel;
wire d_stb;
lm32_top lm32(
.clk_i(sys_clk),
.rst_i(sys_rst),
.interrupt(interrupt),
.ext_break(1'b0),
.I_ACK_I(i_ack),
.I_ADR_O(i_adr),
.I_BTE_O(),
.I_CTI_O(),
.I_CYC_O(i_cyc),
.I_DAT_I(i_dat),
.I_DAT_O(),
.I_ERR_I(1'b0),
.I_LOCK_O(),
.I_RTY_I(1'b0),
.I_SEL_O(),
.I_STB_O(i_stb),
.I_WE_O(),
.D_ACK_I(d_ack),
.D_ADR_O(d_adr),
.D_BTE_O(),
.D_CTI_O(),
.D_CYC_O(d_cyc),
.D_DAT_I(d_dat_i),
.D_DAT_O(d_dat_o),
.D_ERR_I(1'b0),
.D_LOCK_O(),
.D_RTY_I(1'b0),
.D_SEL_O(d_sel),
.D_STB_O(d_stb),
.D_WE_O(d_we)
);
// clock
initial sys_clk = 1'b0;
always #50 sys_clk = ~sys_clk;
// reset
initial begin
sys_rst = 1'b0;
repeat (500) @(posedge sys_clk);
sys_rst = 1'b1;
repeat (500) @(posedge sys_clk);
sys_rst = 1'b0;
end
// memory
reg [7:0] mem[0:65535];
initial begin
for(i=0;i<65536;i=i+1)
mem[i] = 8'b0;
end
// memory monitor bitmap
reg mem_monitor_prog_rd[0:'h4000];
reg mem_monitor_data_rd[0:'h4000];
reg mem_monitor_data_wr[0:'h4000];
initial begin
for(i=0;i<'h4000;i=i+1) begin
// print prog read (until icache is hot) and non-stack data writes
mem_monitor_prog_rd[i] = 1;
mem_monitor_data_rd[i] = 0;
mem_monitor_data_wr[i] = i < 'h3000;
end
end
wire [31:0] dmem_dat_i;
reg [31:0] dmem_dat_o;
wire [13:0] dmem_adr;
wire [3:0] dmem_we;
always @(posedge sys_clk) begin
if(dmem_we[0]) mem[{dmem_adr, 2'b11}] <= dmem_dat_i[7:0];
if(dmem_we[1]) mem[{dmem_adr, 2'b10}] <= dmem_dat_i[15:8];
if(dmem_we[2]) mem[{dmem_adr, 2'b01}] <= dmem_dat_i[23:16];
if(dmem_we[3]) mem[{dmem_adr, 2'b00}] <= dmem_dat_i[31:24];
dmem_dat_o[7:0] <= mem[{dmem_adr, 2'b11}];
dmem_dat_o[15:8] <= mem[{dmem_adr, 2'b10}];
dmem_dat_o[23:16] <= mem[{dmem_adr, 2'b01}];
dmem_dat_o[31:24] <= mem[{dmem_adr, 2'b00}];
if (dmem_we) begin
if (mem_monitor_data_wr[dmem_adr] && !d_ack)
$display("DATA WR: @%08x %02x%02x%02x%02x (%b)", {dmem_adr, 2'b00},
dmem_we[3] ? dmem_dat_i[31:24] : 8'bz,
dmem_we[2] ? dmem_dat_i[23:16] : 8'bz,
dmem_we[1] ? dmem_dat_i[15: 8] : 8'bz,
dmem_we[0] ? dmem_dat_i[ 7: 0] : 8'bz, dmem_we);
end else if (d_cyc) begin
if (mem_monitor_data_rd[dmem_adr] && !d_ack)
$display("DATA RD: @%08x %02x%02x%02x%02x", {dmem_adr, 2'b00},
mem[{dmem_adr, 2'b00}], mem[{dmem_adr, 2'b01}],
mem[{dmem_adr, 2'b10}], mem[{dmem_adr, 2'b11}]);
end
end
reg [31:0] pmem_dat_o;
wire [13:0] pmem_adr;
always @(posedge sys_clk) begin
pmem_dat_o[7:0] <= mem[{pmem_adr, 2'b11}];
pmem_dat_o[15:8] <= mem[{pmem_adr, 2'b10}];
pmem_dat_o[23:16] <= mem[{pmem_adr, 2'b01}];
pmem_dat_o[31:24] <= mem[{pmem_adr, 2'b00}];
if (i_cyc && !i_ack && mem_monitor_prog_rd[pmem_adr])
$display("PROG RD: @%08x %02x%02x%02x%02x", {pmem_adr, 2'b00},
mem[{pmem_adr, 2'b00}], mem[{pmem_adr, 2'b01}],
mem[{pmem_adr, 2'b10}], mem[{pmem_adr, 2'b11}]);
end
// uart
always @(posedge sys_clk) begin
if(d_cyc & d_stb & d_we & d_ack) begin
if(d_adr == 32'hff000000)
$write("%c", d_dat_o[7:0]);
if(d_adr == 32'hff000004)
$display("OUT: %d", d_dat_o);
end
end
// wishbone interface for instruction bus
always @(posedge sys_clk) begin
if(sys_rst)
i_ack <= 1'b0;
else begin
i_ack <= 1'b0;
if(i_cyc & i_stb & ~i_ack)
i_ack <= 1'b1;
end
end
integer clock_counter = 0;
always @(posedge sys_clk) begin
clock_counter = clock_counter + 1;
if (clock_counter == 1000) begin
$display("-- 1000 sys_clk cycles --");
clock_counter = 0;
end
end
assign i_dat = pmem_dat_o;
assign pmem_adr = i_adr[15:2];
`ifdef TB_ENABLE_QEMU
task dump_processor_state;
begin
$display("Processor state:");
$display(" PSW=%08x", lm32.cpu.psw);
$display(" IE=%08x IP=%08x IM=%08x",
lm32.cpu.interrupt_unit.ie,
lm32.cpu.interrupt_unit.ip,
lm32.cpu.interrupt_unit.im
);
for(i=0; i<32; i=i+1) begin
if(i%4 == 0)
$write(" ");
$write("r%02d=%08x ", i, lm32.cpu.reg_0.mem[i]);
if((i+1)%4 == 0)
$write("\n");
end
end
endtask
// QEMU test core
reg [15:0] testname_adr;
reg [8*32:0] testname;
reg testname_end;
always @(posedge sys_clk) begin
if(d_cyc & d_stb & d_we & d_ack)
begin
if(d_adr == 32'hffff0000)
$finish;
else if(d_adr == 32'hffff0004) begin
// is there any better way to do this?
testname_end = 1'b0;
for(i=0; i<32; i=i+1) begin
testname = testname << 8;
if(testname_end == 1'b0) begin
testname[7:0] = mem[testname_adr+i];
if(mem[testname_adr+i] == 8'b0)
testname_end = 1'b1;
end else
testname[7:0] = 8'b0;
end
$display("TC %-32s %s", testname, (|d_dat_o) ? "FAILED" : "OK");
if(|d_dat_o)
dump_processor_state();
end
else if(d_adr == 32'hffff0008)
testname_adr <= d_dat_o[15:0];
end
end
`endif
// wishbone interface for data bus
always @(posedge sys_clk) begin
if(sys_rst)
d_ack <= 1'b0;
else begin
d_ack <= 1'b0;
if(d_cyc & d_stb & ~d_ack)
d_ack <= 1'b1;
end
end
assign d_dat_i = dmem_dat_o;
assign dmem_dat_i = d_dat_o;
assign dmem_adr = d_adr[15:2];
assign dmem_we = {4{d_cyc & d_stb & d_we & ~|d_adr[31:16]}} & d_sel;
// interrupts
initial interrupt <= 32'b0;
// simulation end request
always @(posedge sys_clk) begin
if(d_cyc & d_stb & d_we & d_ack)
if(d_adr == 32'hdead0000 && d_dat_o == 32'hbeef)
$finish;
end
// traces
`ifdef TB_ENABLE_WB_TRACES
always @(posedge sys_clk) begin
if(i_cyc & i_stb & i_ack)
$display("i load @%08x %08x", i_adr, i_dat);
if(d_cyc & d_stb & ~d_we & d_ack)
$display("d load @%08x %08x", d_adr, d_dat_o);
if(d_cyc & d_stb & d_we & d_ack)
$display("d store @%08x %08x", d_adr, d_dat_o);
end
`endif
`ifdef TB_ENABLE_TLB_TRACES
always @(posedge sys_clk)
begin
if (lm32.cpu.instruction_unit.itlb.write_port_enable)
$display("itlb write @%04x 0x%08x",
lm32.cpu.instruction_unit.itlb.write_address,
lm32.cpu.instruction_unit.itlb.write_data);
if (lm32.cpu.load_store_unit.dtlb.write_port_enable)
$display("dtlb write @%04x 0x%08x",
lm32.cpu.instruction_unit.itlb.write_address,
lm32.cpu.instruction_unit.itlb.write_data);
end
`endif
// dump signals
reg [256*8:0] vcdfile;
initial begin
if($value$plusargs("dump=%s", vcdfile)) begin
$dumpfile(vcdfile);
$dumpvars(0, testbench);
end
end
// init memory
reg [256*8:0] prog;
initial begin
if(! $value$plusargs("prog=%s", prog)) begin
// $display("ERROR: please specify +prog=<file>.vh to start.");
// $finish;
prog = "lm32/sim/sieve.vh";
end
end
initial $readmemh(prog, mem);
// trace pipeline
`ifdef TB_ENABLE_PL_TRACES
reg [256*8:0] tracefile;
integer trace_started;
integer trace_enabled;
integer cycle;
integer tracefd;
initial begin
if($value$plusargs("trace=%s", tracefile)) begin
trace_enabled = 1;
cycle = 0;
tracefd = $fopen(tracefile);
trace_started = 0;
end else
trace_enabled = 0;
end
`ifdef CFG_ICACHE_ENABLED
assign icache_ready = lm32.cpu.instruction_unit.icache.state != 1;
`else
assign icache_ready = `TRUE;
`endif
`ifdef CFG_DCACHE_ENABLED
assign dcache_ready = lm32.cpu.load_store_unit.dcache.state != 1;
`else
assign dcache_ready = `TRUE;
`endif
always @(posedge sys_clk) begin
// wait until icache and dcache init is done
if(!trace_started && icache_ready && dcache_ready)
trace_started = 1;
if(trace_enabled && trace_started) begin
$fwrite(tracefd, "%-d ", cycle);
`ifdef CFG_ICACHE_ENABLED
$fwrite(tracefd, "%x ", {lm32.cpu.instruction_unit.pc_a, 2'b00});
$fwrite(tracefd, "%1d ", lm32.cpu.valid_a);
`endif
$fwrite(tracefd, "%x ", {lm32.cpu.instruction_unit.pc_f, 2'b00});
$fwrite(tracefd, "%1d ", lm32.cpu.kill_f);
$fwrite(tracefd, "%1d ", lm32.cpu.valid_f);
$fwrite(tracefd, "%x ", {lm32.cpu.instruction_unit.pc_d, 2'b00});
$fwrite(tracefd, "%1d ", lm32.cpu.kill_d);
$fwrite(tracefd, "%1d ", lm32.cpu.valid_d);
$fwrite(tracefd, "%x ", {lm32.cpu.instruction_unit.pc_x, 2'b00});
$fwrite(tracefd, "%1d ", lm32.cpu.kill_x);
$fwrite(tracefd, "%1d ", lm32.cpu.valid_x);
$fwrite(tracefd, "%x ", {lm32.cpu.instruction_unit.pc_m, 2'b00});
$fwrite(tracefd, "%1d ", lm32.cpu.kill_m);
$fwrite(tracefd, "%1d ", lm32.cpu.valid_m);
$fwrite(tracefd, "%x ", {lm32.cpu.instruction_unit.pc_w, 2'b00});
$fwrite(tracefd, "%1d ", lm32.cpu.kill_w);
$fwrite(tracefd, "%1d ", lm32.cpu.valid_w);
$fwrite(tracefd, "\n");
cycle = cycle + 1;
end
end
`endif
initial begin
// $dumpfile("bench.vcd");
// @(posedge sys_rst);
// repeat (5) @(posedge sys_clk);
// $dumpvars(0, testbench);
// repeat (2500) @(posedge sys_clk);
// $finish;
end
endmodule