///////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
//
|
//
|
// Filename: pfcache_tb.cpp
|
// Filename: pfcache_tb.cpp
|
//
|
//
|
// Project: Zip CPU -- a small, lightweight, RISC CPU soft core
|
// Project: Zip CPU -- a small, lightweight, RISC CPU soft core
|
//
|
//
|
// Purpose: Bench testing for the prefetch cache used within the ZipCPU
|
// Purpose: Bench testing for the prefetch cache used within the ZipCPU
|
// when it is in pipelind mode. Whether or not this module is
|
// when it is in pipelind mode. Whether or not this module is
|
// used depends upon how the CPU is set up in cpudefs.v.
|
// used depends upon how the CPU is set up in cpudefs.v.
|
//
|
//
|
//
|
//
|
// Creator: Dan Gisselquist, Ph.D.
|
// Creator: Dan Gisselquist, Ph.D.
|
// Gisselquist Technology, LLC
|
// Gisselquist Technology, LLC
|
//
|
//
|
///////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
//
|
//
|
// Copyright (C) 2015-2017, Gisselquist Technology, LLC
|
// Copyright (C) 2015-2017, Gisselquist Technology, LLC
|
//
|
//
|
// This program is free software (firmware): you can redistribute it and/or
|
// This program is free software (firmware): you can redistribute it and/or
|
// modify it under the terms of the GNU General Public License as published
|
// modify it under the terms of the GNU General Public License as published
|
// by the Free Software Foundation, either version 3 of the License, or (at
|
// by the Free Software Foundation, either version 3 of the License, or (at
|
// your option) any later version.
|
// your option) any later version.
|
//
|
//
|
// This program is distributed in the hope that it will be useful, but WITHOUT
|
// This program is distributed in the hope that it will be useful, but WITHOUT
|
// ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or
|
// ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or
|
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
// for more details.
|
// for more details.
|
//
|
//
|
|
// You should have received a copy of the GNU General Public License along
|
|
// with this program. (It's in the $(ROOT)/doc directory. Run make with no
|
|
// target there if the PDF file isn't present.) If not, see
|
|
// <http://www.gnu.org/licenses/> for a copy.
|
|
//
|
// License: GPL, v3, as defined and found on www.gnu.org,
|
// License: GPL, v3, as defined and found on www.gnu.org,
|
// http://www.gnu.org/licenses/gpl.html
|
// http://www.gnu.org/licenses/gpl.html
|
//
|
//
|
//
|
//
|
///////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
//
|
//
|
//
|
//
|
#include <signal.h>
|
#include <signal.h>
|
#include <time.h>
|
#include <time.h>
|
#include <unistd.h>
|
#include <unistd.h>
|
#include <assert.h>
|
#include <assert.h>
|
|
|
#include <stdlib.h>
|
#include <stdlib.h>
|
#include <ctype.h>
|
#include <ctype.h>
|
|
|
#include "verilated.h"
|
#include "verilated.h"
|
#include "verilated_vcd_c.h"
|
#include "verilated_vcd_c.h"
|
#include "Vpfcache.h"
|
#include "Vpfcache.h"
|
|
|
#include "testb.h"
|
#include "testb.h"
|
#include "cpudefs.h"
|
#include "cpudefs.h"
|
#include "memsim.h"
|
#include "memsim.h"
|
|
|
#define RAMBASE (1<<20)
|
#define RAMBASE (1<<20)
|
#define RAMWORDS RAMBASE
|
#define RAMWORDS RAMBASE
|
#define MAXTIMEOUT 128
|
#define MAXTIMEOUT 128
|
|
|
FILE *gbl_dbgfp = NULL; // Can also be set to stdout
|
FILE *gbl_dbgfp = NULL; // Can also be set to stdout
|
|
|
class PFCACHE_TB : public TESTB<Vpfcache> {
|
class PFCACHE_TB : public TESTB<Vpfcache> {
|
public:
|
public:
|
MEMSIM m_mem;
|
MEMSIM m_mem;
|
bool m_bomb;
|
bool m_bomb;
|
|
|
// Nothing special to do in a startup.
|
// Nothing special to do in a startup.
|
PFCACHE_TB(void) : m_mem(RAMWORDS), m_bomb(false) {}
|
PFCACHE_TB(void) : m_mem(RAMWORDS), m_bomb(false) {}
|
|
|
void randomize_memory(void) {
|
void randomize_memory(void) {
|
m_mem.load("/dev/urandom");
|
m_mem.load("/dev/urandom");
|
}
|
}
|
|
|
// ~CPUOPS_TB(void) {}
|
// ~CPUOPS_TB(void) {}
|
|
|
//
|
//
|
// Calls TESTB<>::reset to reset the core. Makes sure the i_ce line
|
// Calls TESTB<>::reset to reset the core. Makes sure the i_ce line
|
// is low during this reset.
|
// is low during this reset.
|
//
|
//
|
void reset(void) {
|
void reset(void) {
|
m_core->i_rst = 0;
|
m_core->i_reset = 0;
|
m_core->i_pc = RAMBASE;
|
m_core->i_pc = RAMBASE<<2;
|
m_core->i_new_pc = 0;
|
m_core->i_new_pc = 0;
|
m_core->i_clear_cache = 1;
|
m_core->i_clear_cache = 1;
|
m_core->i_stall_n = 1;
|
m_core->i_stall_n = 1;
|
m_core->i_stall_n = 1;
|
m_core->i_stall_n = 1;
|
|
|
TESTB<Vpfcache>::reset();
|
TESTB<Vpfcache>::reset();
|
}
|
}
|
|
|
//
|
//
|
// dbgdump();
|
// dbgdump();
|
//
|
//
|
void dbgdump(void) {
|
void dbgdump(void) {
|
/*
|
/*
|
char outstr[2048], *s;
|
char outstr[2048], *s;
|
sprintf(outstr, "Tick %4ld %s%s ",
|
sprintf(outstr, "Tick %4ld %s%s ",
|
m_tickcount,
|
m_tickcount,
|
(m_core->i_rst)?"R":" ",
|
(m_core->i_rst)?"R":" ",
|
(m_core->i_ce)?"CE":" ");
|
(m_core->i_ce)?"CE":" ");
|
s = &outstr[strlen(outstr)];
|
s = &outstr[strlen(outstr)];
|
|
|
puts(outstr);
|
puts(outstr);
|
*/
|
*/
|
}
|
}
|
|
|
bool valid_mem(uint32_t addr) {
|
bool valid_mem(uint32_t addr) {
|
if (addr < RAMBASE)
|
if (addr < RAMBASE)
|
return false;
|
return false;
|
if (addr >= RAMBASE + RAMWORDS)
|
if (addr >= RAMBASE + RAMWORDS)
|
return false;
|
return false;
|
return true;
|
return true;
|
}
|
}
|
|
|
//
|
//
|
// tick()
|
// tick()
|
//
|
//
|
// Call this to step the module under test.
|
// Call this to step the module under test.
|
//
|
//
|
void tick(void) {
|
void tick(void) {
|
bool debug = false;
|
bool debug = false;
|
|
|
if ((m_core->o_wb_cyc)&&(m_core->o_wb_stb)) {
|
if ((m_core->o_wb_cyc)&&(m_core->o_wb_stb)) {
|
if (!valid_mem(m_core->o_wb_addr))
|
if (!valid_mem(m_core->o_wb_addr))
|
m_core->i_wb_err = 1;
|
m_core->i_wb_err = 1;
|
} else if (m_core->o_wb_stb) {
|
} else if (m_core->o_wb_stb) {
|
m_bomb = true;
|
m_bomb = true;
|
}
|
}
|
|
|
if (m_core->o_wb_we)
|
if (m_core->o_wb_we)
|
m_bomb = true;
|
m_bomb = true;
|
if (m_core->o_wb_data != 0)
|
if (m_core->o_wb_data != 0)
|
m_bomb = true;
|
m_bomb = true;
|
|
|
if (debug)
|
if (debug)
|
dbgdump();
|
dbgdump();
|
|
|
unsigned mask = (RAMBASE-1);
|
unsigned mask = (RAMBASE-1);
|
|
|
m_mem(m_core->o_wb_cyc, m_core->o_wb_stb, m_core->o_wb_we,
|
m_mem(m_core->o_wb_cyc, m_core->o_wb_stb, m_core->o_wb_we,
|
m_core->o_wb_addr & mask, m_core->o_wb_data, 0,
|
m_core->o_wb_addr & mask, m_core->o_wb_data, 0,
|
m_core->i_wb_ack, m_core->i_wb_stall,m_core->i_wb_data);
|
m_core->i_wb_ack, m_core->i_wb_stall,m_core->i_wb_data);
|
|
|
TESTB<Vpfcache>::tick();
|
TESTB<Vpfcache>::tick();
|
|
|
if (m_core->o_v) {
|
if (m_core->o_valid) {
|
uint32_t pc, insn;
|
uint32_t pc, insn;
|
|
|
pc = m_core->o_pc;
|
pc = m_core->o_pc;
|
insn = m_core->o_i;
|
insn = m_core->o_insn;
|
if (insn != m_mem[pc & (RAMWORDS-1)]) {
|
if (insn != m_mem[(pc>>2) & (RAMWORDS-1)]) {
|
fprintf(stderr, "ERR: PF[%08x] = %08x != %08x\n", pc,
|
fprintf(stderr, "ERR: PF[%08x] = %08x != %08x\n", pc,
|
insn, m_mem[pc & (RAMWORDS-1)]);
|
insn, m_mem[(pc>>2) & (RAMWORDS-1)]);
|
closetrace();
|
closetrace();
|
assert(insn == m_mem[pc & (RAMWORDS-1)]);
|
assert(insn == m_mem[(pc>>2) & (RAMWORDS-1)]);
|
}
|
}
|
}
|
}
|
}
|
}
|
|
|
//
|
//
|
// fetch_insn()
|
// fetch_insn()
|
//
|
//
|
void fetch_insn(void) {
|
void fetch_insn(void) {
|
uint32_t timeout = 0;
|
uint32_t timeout = 0;
|
|
|
if ((m_core->o_v)&&(m_core->i_stall_n))
|
if ((m_core->o_valid)&&(m_core->i_stall_n))
|
m_core->i_pc++;
|
m_core->i_pc++;
|
|
|
m_core->i_rst = 0;
|
m_core->i_reset = 0;
|
m_core->i_new_pc = 0;
|
m_core->i_new_pc = 0;
|
m_core->i_clear_cache = 0;
|
m_core->i_clear_cache = 0;
|
m_core->i_stall_n = 1;
|
m_core->i_stall_n = 1;
|
do {
|
do {
|
tick();
|
tick();
|
} while((!m_core->o_v)&&(!m_core->o_illegal)&&(timeout++ < MAXTIMEOUT));
|
} while((!m_core->o_valid)&&(!m_core->o_illegal)&&(timeout++ < MAXTIMEOUT));
|
|
|
if (timeout >= MAXTIMEOUT)
|
if (timeout >= MAXTIMEOUT)
|
m_bomb = true;
|
m_bomb = true;
|
}
|
}
|
|
|
//
|
//
|
// skip_fetch()
|
// skip_fetch()
|
//
|
//
|
void skip_fetch(void) {
|
void skip_fetch(void) {
|
uint32_t prevalid, insn;
|
uint32_t prevalid, insn;
|
|
|
if ((m_core->o_v)&&(m_core->i_stall_n))
|
if ((m_core->o_valid)&&(m_core->i_stall_n))
|
m_core->i_pc++;
|
m_core->i_pc++;
|
|
|
m_core->i_rst = 0;
|
m_core->i_reset = 0;
|
m_core->i_new_pc = 0;
|
m_core->i_new_pc = 0;
|
m_core->i_clear_cache = 0;
|
m_core->i_clear_cache = 0;
|
m_core->i_stall_n = 0;
|
m_core->i_stall_n = 0;
|
insn = m_core->o_i;
|
insn = m_core->o_insn;
|
prevalid= m_core->o_v;
|
prevalid= m_core->o_valid;
|
|
|
tick();
|
tick();
|
|
|
if (prevalid) {
|
if (prevalid) {
|
// if (!m_core->o_v) {
|
// if (!m_core->o_valid) {
|
// fprintf(stderr, "ERR: VALID dropped on stall!\n");
|
// fprintf(stderr, "ERR: VALID dropped on stall!\n");
|
// closetrace();
|
// closetrace();
|
// assert(m_core->o_v);
|
// assert(m_core->o_valid);
|
// }
|
// }
|
if (insn != m_core->o_i) {
|
if (insn != m_core->o_insn) {
|
fprintf(stderr, "ERR: VALID INSN CHANGED on stall!\n");
|
fprintf(stderr, "ERR: VALID INSN CHANGED on stall!\n");
|
closetrace();
|
closetrace();
|
assert(insn == m_core->o_i);
|
assert(insn == m_core->o_insn);
|
}
|
}
|
}
|
}
|
}
|
}
|
|
|
|
|
//
|
//
|
// jump
|
// jump
|
//
|
//
|
void jump(unsigned target) {
|
void jump(unsigned target) {
|
uint32_t timeout = 0;
|
uint32_t timeout = 0;
|
|
|
m_core->i_rst = 0;
|
m_core->i_reset = 0;
|
m_core->i_new_pc = 1;
|
m_core->i_new_pc = 1;
|
m_core->i_clear_cache = 0;
|
m_core->i_clear_cache = 0;
|
m_core->i_stall_n = 1;
|
m_core->i_stall_n = 1;
|
m_core->i_pc = target;
|
m_core->i_pc = target;
|
|
|
tick();
|
tick();
|
m_core->i_pc++;
|
m_core->i_pc++;
|
m_core->i_new_pc = 0;
|
m_core->i_new_pc = 0;
|
m_core->i_stall_n = 0;
|
m_core->i_stall_n = 0;
|
|
|
while((!m_core->o_v)&&(timeout++ < MAXTIMEOUT))
|
while((!m_core->o_valid)&&(timeout++ < MAXTIMEOUT))
|
tick();
|
tick();
|
|
|
if (timeout >= MAXTIMEOUT)
|
if (timeout >= MAXTIMEOUT)
|
m_bomb = true;
|
m_bomb = true;
|
if (m_core->o_v)
|
if (m_core->o_valid)
|
assert(m_core->o_pc == target);
|
assert(m_core->o_pc == target);
|
}
|
}
|
};
|
};
|
|
|
void usage(void) {
|
void usage(void) {
|
printf("USAGE: pfcache_tb\n");
|
printf("USAGE: pfcache_tb\n");
|
printf("\n");
|
printf("\n");
|
}
|
}
|
|
|
int main(int argc, char **argv) {
|
int main(int argc, char **argv) {
|
// Setup verilator
|
// Setup verilator
|
Verilated::commandArgs(argc, argv);
|
Verilated::commandArgs(argc, argv);
|
// Now, create a test bench.
|
// Now, create a test bench.
|
PFCACHE_TB *tb = new PFCACHE_TB();
|
PFCACHE_TB *tb = new PFCACHE_TB();
|
int rcode = EXIT_SUCCESS;
|
int rcode = EXIT_SUCCESS;
|
|
|
tb->opentrace("pfcache.vcd");
|
tb->opentrace("pfcache.vcd");
|
tb->randomize_memory();
|
tb->randomize_memory();
|
|
|
tb->jump(RAMBASE);
|
tb->jump(RAMBASE<<2);
|
|
|
// Simulate running straight through code
|
// Simulate running straight through code
|
for(int i=0; i<130; i++) {
|
for(int i=0; i<130; i++) {
|
// printf("FETCH\n");
|
// printf("FETCH\n");
|
tb->fetch_insn();
|
tb->fetch_insn();
|
}
|
}
|
|
|
// Now, let's bounce around through the cache
|
// Now, let's bounce around through the cache
|
for(int j=0; j<20; j++) {
|
for(int j=0; j<20; j++) {
|
tb->jump(RAMBASE+j);
|
tb->jump((RAMBASE+j)<<2);
|
for(int i=0; i<130; i++) {
|
for(int i=0; i<130; i++) {
|
// printf("FETCH\n");
|
// printf("FETCH\n");
|
tb->fetch_insn();
|
tb->fetch_insn();
|
}
|
}
|
|
|
if (tb->m_bomb) {
|
if (tb->m_bomb) {
|
printf("TEST FAILURE!\n");
|
printf("TEST FAILURE!\n");
|
delete tb;
|
delete tb;
|
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
}
|
}
|
}
|
}
|
|
|
// Now, add in some CIS-type instructions
|
// Now, add in some CIS-type instructions
|
for(int i=0; i<130; i++) {
|
for(int i=0; i<130; i++) {
|
unsigned v = rand() & 0x0f;
|
unsigned v = rand() & 0x0f;
|
|
|
if ((v&3)==2) {
|
if ((v&3)==2) {
|
// printf("SKIP\n");
|
// printf("SKIP\n");
|
tb->skip_fetch();
|
tb->skip_fetch();
|
} else {
|
} else {
|
// printf("FETCH\n");
|
// printf("FETCH\n");
|
tb->fetch_insn();
|
tb->fetch_insn();
|
}
|
}
|
|
|
if (tb->m_bomb) {
|
if (tb->m_bomb) {
|
printf("TEST FAILURE!\n");
|
printf("TEST FAILURE!\n");
|
delete tb;
|
delete tb;
|
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
}
|
}
|
}
|
}
|
|
|
// Finally, try it all: stalls, CIS, and jumps
|
// Finally, try it all: stalls, CIS, and jumps
|
for(int i=0; i<10000; i++) {
|
for(int i=0; i<10000; i++) {
|
unsigned v = rand() & 0x0f;
|
unsigned v = rand() & 0x0f;
|
if (v == 0) {
|
if (v == 0) {
|
uint32_t target = rand() & (RAMWORDS-1);
|
uint32_t target = rand() & (RAMWORDS-1);
|
target += RAMBASE;
|
target += RAMBASE;
|
// printf("JUMP TO %08x\n", target);
|
// printf("JUMP TO %08x\n", target);
|
tb->jump(target);
|
tb->jump(target<<2);
|
} else if ((v & 3)==2) {
|
} else if ((v & 3)==2) {
|
// printf("SKIP\n");
|
// printf("SKIP\n");
|
tb->skip_fetch();
|
tb->skip_fetch();
|
} else {
|
} else {
|
// printf("FETCH\n");
|
// printf("FETCH\n");
|
tb->fetch_insn();
|
tb->fetch_insn();
|
}
|
}
|
|
|
if (tb->m_bomb) {
|
if (tb->m_bomb) {
|
printf("TEST FAILURE!\n");
|
printf("TEST FAILURE!\n");
|
delete tb;
|
delete tb;
|
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
}
|
}
|
}
|
}
|
|
|
printf("SUCCESS!\n");
|
printf("SUCCESS!\n");
|
exit(rcode);
|
exit(rcode);
|
}
|
}
|
|
|
|
|