URL
https://opencores.org/ocsvn/zipcpu/zipcpu/trunk
Subversion Repositories zipcpu
[/] [zipcpu/] [trunk/] [bench/] [asm/] [cputest.c] - Rev 208
Go to most recent revision | Compare with Previous | Blame | View Log
/////////////////////////////////////////////////////////////////////////////// // // Filename: cputest.c // // Project: Zip CPU -- a small, lightweight, RISC CPU soft core // // Purpose: To test the CPU, it's instructions, cache, and pipeline, to make // certain that it works. This includes testing that each of the // instructions works, as well as any strange instruction combinations. // // // Creator: Dan Gisselquist, Ph.D. // Gisselquist Technology, LLC // /////////////////////////////////////////////////////////////////////////////// // // Copyright (C) 2015-2016, Gisselquist Technology, LLC // // 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 // by the Free Software Foundation, either version 3 of the License, or (at // your option) any later version. // // This program is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY or // FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License // for more details. // // License: GPL, v3, as defined and found on www.gnu.org, // http://www.gnu.org/licenses/gpl.html // // /////////////////////////////////////////////////////////////////////////////// // // #ifndef NULL #define NULL (void *)0 #endif volatile int *const UARTTX = (volatile int *)0x010b, * const UART_CTRL = (int *)0x0107; // #define ZIPSYS #ifdef ZIPSYS #define HAVE_COUNTER volatile int * const PIC = (volatile int *)0xc0000000; const int INT_UARTTX = 0x2000; volatile int *const COUNTER = (volatile int *)0xc0000008; #define HAVE_SCOPE #else volatile int * const PIC = (volatile int *)0x0102; const int INT_UARTTX = 0x080; volatile int *const TIMER = (volatile int *)0x0104; #define HAVE_TIMER #endif #ifdef HAVE_SCOPE volatile int *const SCOPE = (volatile int *)0x011e; #endif unsigned zip_ucc(void); void zip_save_context(int *); void zip_halt(void); void txchr(char v); void txstr(const char *str); void txhex(int num); void tx4hex(int num); asm("\t.section\t.start\n" "\t.global\t_start\n" "\t.type\t_start,@function\n" "_start:\n" #ifdef HAVE_SCOPE "\tNOOP\n" "\tLOD\t286,R0\n" "\tROL\t16,R0\n" "\tTST\t0x1000,R0\n" "\tBZ\t_start\n" "\tOR\t0x0f00,R0\n" "\tROL\t16,R0\n" "\tSTO\tR0,286\n" #endif "\tCLR\tR0\n" "\tCLR\tR1\n" "\tCLR\tR2\n" "\tCLR\tR3\n" "\tCLR\tR4\n" "\tCLR\tR5\n" "\tCLR\tR6\n" "\tCLR\tR7\n" "\tCLR\tR8\n" "\tCLR\tR9\n" "\tCLR\tR10\n" "\tCLR\tR11\n" "\tCLR\tR12\n" "\tLDI\t_top_of_stack,SP\n" "\tCLR\tCC\n" "\tMOV\tbusy_failure(PC),R0\n" "\tBRA\tentry\n" "busy_failure:\n" "\tBUSY\n" "\t.section\t.text"); #ifdef HAVE_COUNTER #define MARKSTART start_time = *COUNTER #define MARKSTOP stop_time = *COUNTER #else #ifdef HAVE_TIMER #define MARKSTART start_time = *TIMER #define MARKSTOP stop_time = *TIMER #else #define MARKSTART #define MARKSTOP #endif #endif extern int run_test(void *pc, void *stack); asm("\t.text\n\t.global\trun_test\n" "\t.type\trun_test,@function\n" "run_test:\n" "\tCLR\tR3\n" "\tMOV\ttest_return(PC),uR0\n" "\tMOV\tR3,uR1\n" "\tMOV\tR3,uR2\n" "\tMOV\tR3,uR3\n" "\tMOV\tR3,uR4\n" "\tMOV\tR3,uR5\n" "\tMOV\tR3,uR6\n" "\tMOV\tR3,uR7\n" "\tMOV\tR3,uR8\n" "\tMOV\tR3,uR9\n" "\tMOV\tR3,uR10\n" "\tMOV\tR3,uR11\n" "\tMOV\tR3,uR12\n" "\tMOV\tR2,uSP\n" // uSP = stack "\tMOV\t0x20+R3,uCC\n" // Clear uCC of all but the GIE bit "\tMOV\tR1,uPC\n" // uPC = pc "\tRTU\n" "test_return:\n" "\tMOV\tuR1,R1\n" "\tAND\t0xffffffdf,CC\n" // Works with 5 NOOPS, works with 3 NOOPS, works with 1 NOOP "\tJMP\tR0\n"); extern int idle_test(void); asm("\t.text\n\t.global\tidle_test\n" "\t.type\tidle_test,@function\n" "idle_test:\n" "\tCLR\tR1\n" "\tMOV\tidle_loop(PC),uR0\n" "\tMOV\tR1,uR1\n" "\tMOV\tR1,uR2\n" "\tMOV\tR1,uR3\n" "\tMOV\tR1,uR4\n" "\tMOV\tR1,uR5\n" "\tMOV\tR1,uR6\n" "\tMOV\tR1,uR7\n" "\tMOV\tR1,uR8\n" "\tMOV\tR1,uR9\n" "\tMOV\tR1,uR10\n" "\tMOV\tR1,uR11\n" "\tMOV\tR1,uR12\n" "\tMOV\tR1,uSP\n" "\tMOV\t0x20+R1,uCC\n" "\tMOV\tidle_loop(PC),uPC\n" "\tWAIT\n" "\tMOV uPC,R1\n" "\tCMP idle_loop(PC),R1\n" "\tLDI 0,R1\n" "\tLDI.NZ 1,R1\n" "\nRETN\n" "idle_loop:\n" "\tWAIT\n" "\tBRA idle_loop\n"); void break_one(void); asm("\t.text\n\t.global\tbreak_one\n" "\t.type\tbreak_one,@function\n" "break_one:\n" "\tLDI\t0,R1\n" "\tBREAK\n" "\tLDI\t1,R1\n" // Test fails "\tJMP\tR0"); void break_two(void); // Can we stop a break before we hit it? asm("\t.text\n\t.global\tbreak_two\n" "\t.type\tbreak_two,@function\n" "break_two:\n" "\tLDI\t0,R1\n" "\tJMP\tR0\n" "\tBREAK\n"); void early_branch_test(void); asm("\t.text\n\t.global\tearly_branch_test\n" "\t.type\tearly_branch_test,@function\n" "early_branch_test:\n" "\tLDI\t1,R1\n" "\tBRA\t_eb_a\n" "\tBREAK\n" "_eb_a:\n" "\tLDI\t2,R1\n" "\tBRA\t_eb_b\n" "\tNOP\n" "\tBREAK\n" "_eb_b:\n" "\tLDI\t3,R1\n" "\tBRA\t_eb_c\n" "\tNOP\n" "\tNOP\n" "\tBREAK\n" "_eb_c:\n" "\tLDI\t4,R1\n" "\tBRA\t_eb_d\n" "\tNOP\n" "\tNOP\n" "\tNOP\n" "\tBREAK\n" "_eb_d:\n" "\tLDI\t5,R1\n" "\tBRA\t_eb_e\n" "\tNOP\n" "\tNOP\n" "\tNOP\n" "\tNOP\n" "\tBREAK\n" "_eb_e:\n" "\tLDI\t6,R1\n" "\tBRA\t_eb_f\n" "\tNOP\n" "\tNOP\n" "\tNOP\n" "\tNOP\n" "\tNOP\n" "\tBREAK\n" "_eb_f:\n" "\tLDI\t0,R1\n" "\tJMP\tR0"); void trap_test_and(void); asm("\t.text\n\t.global\ttrap_test_and\n" "\t.type\ttrap_test_and,@function\n" "trap_test_and:\n" "\tLDI\t0,R1\n" "\tAND\t0xffffffdf,CC\n" "\tLDI\t1,R1\n" // Test fails "\tJMP\tR0"); void trap_test_clr(void); asm("\t.text\n\t.global\ttrap_test_clr\n" "\t.type\ttrap_test_clr,@function\n" "trap_test_clr:\n" "\tLDI\t0,R1\n" "\tCLR\tCC\n" "\tLDI\t1,R1\n" // Test fails "\tJMP\tR0"); void overflow_test(void); asm("\t.text\n\t.global\toverflow_test\n" "\t.type\toverflow_test,@function\n" "overflow_test:\n" "\tLDI\t0,R1\n" "\tLDI\t0,R3\n" // Clear our scorecard // First test: does adding one to the maximum integer cause an overflow? "\tLDI\t-1,R2\n" "\tLSR\t1,R2\n" "\tADD\t1,R2\n" "\tOR.V\t1,R3\n" // Second test: does subtracting one to the minimum integer cause an overflow? "\tLDI\t0x80000000,R2\n" "\tSUB\t1,R2\n" "\tOR.V\t2,R3\n" // Third test, overflow from LSR "\tLDI\t0x80000000,R2\n" "\tLSR\t1,R2\n" // Overflows 'cause the sign changes "\tOR.V\t4,R3\n" // Fourth test, overflow from LSL "\tLDI\t0x40000000,R2\n" "\tLSL\t1,R2\n" "\tOR.V\t8,R3\n" // Fifth test, overflow from LSL, negative to positive "\tLDI\t0x80000000,R2\n" "\tLSL\t1,R2\n" "\tOR.V\t16,R3\n" // Record our scores "\tXOR\t31,R3\n" "\tOR\tR3,R1\n" // And return the results "\tJMP\tR0"); void carry_test(void); asm("\t.text\n\t.global\tcarry_test\n" "\t.type\tcarry_test,@function\n" "carry_test:\n" "\tLDI\t0,R1\n" "\tLDI\t0,R3\n" // First, in adding "\tLDI\t-1,R2\n" "\tADD\t1,R2\n" "\tOR.C\t1,R3\n" // Then, in subtraction "\tSUB\t1,R2\n" "\tOR.C\t2,R3\n" // From a right shift "\tLDI\t1,R2\n" "\tLSR\t1,R2\n" "\tOR.C\t4,R3\n" "\tLDI\t1,R2\n" "\tASR\t1,R2\n" "\tOR.C\t8,R3\n" // Or from a compare "\tLDI\t0,R2\n" "\tCMP\t1,R2\n" "\tOR.C\t16,R3\n" // Set our return and clean up "\tXOR\t31,R3\n" "\tOR\tR3,R1\n" "\tJMP\tR0"); void loop_test(void); asm("\t.text\n\t.global\tloop_test\n" "\t.type\tloop_test,@function\n" "loop_test:\n" "\tLDI\t0,R1\n" // Let's try a loop: for(i=0; i<5; i++) "\tLDI\t0,R2\n" "\tLDI\t0,R3\n" "\tCMP\t5,R2\n" "\tBGE\tend_for_loop_test\n" "for_loop_test:" "\tADD\t1,R2\n" "\tADD\t1,R3\n" "\tCMP\t5,R2\n" "\tBLT\tfor_loop_test\n" "end_for_loop_test:" "\tCMP\t5,R3\n" "\tOR.NZ\t1,R1\n" // How about a reverse do{} while loop? These are usually cheaper than for() // loops. "\tLDI\t0,R2\n" "\tLDI\t5,R3\n" "bgt_loop_test:\n" "\tADD\t1,R2\n" "\tSUB\t1,R3\n" "\tBGT\tbgt_loop_test\n" "\tCMP\t5,R2\n" "\tOR.NZ\t2,R1\n" // What if we use >=? "\tLDI\t0,R2\n" "\tLDI\t5,R3\n" "bge_loop_test:\n" "\tADD\t1,R2\n" "\tSUB\t1,R3\n" "\tBGE\tbge_loop_test\n" "\tCMP\t6,R2\n" "\tOR.NZ\t4,R1\n" // Once more with the reverse loop, this time storing the loop variable in // memory "\tSUB\t1,SP\n" "\tLDI\t0,R2\n" "\tLDI\t5,R3\n" "\tSTO\tR3,(SP)\n" "mem_loop_test:\n" "\tADD\t1,R2\n" "\tADD\t14,R3\n" "\tLOD\t(SP),R3\n" "\tSUB\t1,R3\n" "\tSTO\tR3,(SP)\n" "\tBGT\tmem_loop_test\n" "\tCMP\t5,R2\n" "\tOR.NZ\t8,R1\n" "\tADD\t1,SP\n" // "\tJMP\tR0\n"); // Test whether or not LSL, LSR, and ASR instructions work, together with their // carry flags void shift_test(void); asm("\t.text\n\t.global\tshift_test\n" "\t.type\tshift_test,@function\n" "shift_test:\n" "\tLDI\t0,R1\n" "\tLDI\t0,R3\n" "\tLDI\t0,R4\n" // Does shifting right by 32 result in a zero? "\tLDI\t-1,R2\n" "\tLSR\t32,R2\n" "\tOR.Z\t1,R3\n" "\tOR.C\t2,R3\n" "\tCMP\t0,R2\n" "\tOR.Z\t4,R3\n" // Does shifting a -1 right arithmetically by 32 result in a -1? "\tLDI\t-1,R2\n" "\tASR\t32,R2\n" "\tOR.LT\t8,R3\n" "\tOR.C\t16,R3\n" "\tCMP\t-1,R2\n" "\tOR.Z\t32,R3\n" // Does shifting a -4 right arithmetically by 2 result in a -1? "\tLDI\t-4,R2\n" "\tASR\t2,R2\n" "\tOR.LT\t64,R3\n" "\tOR.C\t128,R1\n" "\tOR\t128,R3\n" // Artificially declare passing, so as not to fail it "\tCMP\t-1,R2\n" "\tOR.Z\t256,R3\n" // Does one more set the carry flag as desired? "\tASR\t1,R2\n" "\tOR.LT\t512,R3\n" "\tOR.C\t1024,R3\n" "\tCMP\t-1,R2\n" "\tOR.Z\t2048,R3\n" // Does shifting -1 left by 32 result in a zero? "\tLDI\t-1,R2\n" "\tLSL\t32,R2\n" "\tOR.Z\t4096,R3\n" "\tOR.C\t8192,R3\n" "\tCMP\t0,R2\n" "\tOR.Z\t16384,R3\n" // How about shifting by zero? "\tLDI\t-1,R2\n" "\tASR\t0,R2\n" "\tOR.C\t32768,R1\n" "\tOR\t32768,R3\n" "\tCMP\t-1,R2\n" "\tOR.Z\t1,R4\n" // "\tLSR\t0,R2\n" "\tOR.C\t131072,R1\n" "\tCMP\t-1,R2\n" "\tOR.Z\t2,R4\n" // "\tLSL\t0,R2\n" "\tOR.C\t524288,R1\n" "\tCMP\t-1,R2\n" "\tOR.Z\t4,R4\n" // Tally up our results and return "\tXOR\t7,R4\n" "\tXOR\t65535,R3\n" "\tLSL\t16,R4\n" "\tOR\tR4,R3\n" "\tOR\tR3,R1\n" "\tJMP\tR0"); int sw_brev(int v); asm("\t.text\n\t.global\tsw_brev\n" "\t.type\tsw_brev,@function\n" "sw_brev:\n" "\tSUB\t2,SP\n" "\tSTO\tR2,(SP)\n" "\tSTO\tR3,1(SP)\n" "\tLDI\t-1,R2\n" "\tCLR\tR3\n" "sw_brev_loop:\n" "\tLSL\t1,R3\n" "\tLSR\t1,R1\n" "\tOR.C\t1,R3\n" "\tLSR\t1,R2\n" "\tBZ\tsw_brev_endloop\n" "\tBRA\tsw_brev_loop\n" "sw_brev_endloop:\n" "\tMOV\tR3,R1\n" "\tLOD\t(SP),R2\n" "\tLOD\t1(SP),R3\n" "\tADD\t2,SP\n" "\tJMP\tR0"); void pipeline_stack_test(void); asm("\t.text\n\t.global\tpipeline_stack_test\n" "\t.type\tpipeline_stack_test,@function\n" "pipeline_stack_test:\n" "\tSUB\t1,SP\n" "\tSTO\tR0,(SP)\n" "\tLDI\t0,R0\n" "\tMOV\t1(R0),R1\n" "\tMOV\t1(R1),R2\n" "\tMOV\t1(R2),R3\n" "\tMOV\t1(R3),R4\n" "\tMOV\t1(R4),R5\n" "\tMOV\t1(R5),R6\n" "\tMOV\t1(R6),R7\n" "\tMOV\t1(R7),R8\n" "\tMOV\t1(R8),R9\n" "\tMOV\t1(R9),R10\n" "\tMOV\t1(R10),R11\n" "\tMOV\t1(R11),R12\n" "\tMOV\tpipeline_stack_test_component_return(PC),R0\n" // "\tLJMP\tpipeline_stack_test_component\n" "\tBRA\tpipeline_stack_test_component\n" "pipeline_stack_test_component_return:\n" "\tCMP\t1,R1\n" "\tLDI.Z\t0,R1\n" "\tCMP\t2,R2\n" "\tCMP.Z\t3,R3\n" "\tCMP.Z\t4,R4\n" "\tCMP.Z\t5,R5\n" "\tCMP.Z\t6,R6\n" "\tCMP.Z\t7,R7\n" "\tCMP.Z\t8,R8\n" "\tCMP.Z\t9,R9\n" "\tCMP.Z\t10,R10\n" "\tCMP.Z\t11,R11\n" "\tCMP.Z\t12,R12\n" "\tBREV.NZ\t-1,R1\n" "\tLOD\t(SP),R0\n" "\tADD\t1,SP\n" "\tJMP\tR0\n" ); void pipeline_stack_test_component(void); asm("\t.text\n\t.global\tpipeline_stack_test_component\n" "\t.type\tpipeline_stack_test_component,@function\n" "pipeline_stack_test_component:\n" "\tSUB\t13,SP\n" "\tSTO\tR0,(SP)\n" "\tSTO\tR1,1(SP)\n" "\tSTO\tR2,2(SP)\n" "\tSTO\tR3,3(SP)\n" "\tSTO\tR4,4(SP)\n" "\tSTO\tR5,5(SP)\n" "\tSTO\tR6,6(SP)\n" "\tSTO\tR7,7(SP)\n" "\tSTO\tR8,8(SP)\n" "\tSTO\tR9,9(SP)\n" "\tSTO\tR10,10(SP)\n" "\tSTO\tR11,11(SP)\n" "\tSTO\tR12,12(SP)\n" "\tXOR\t-1,R0\n" "\tXOR\t-1,R1\n" "\tXOR\t-1,R2\n" "\tXOR\t-1,R3\n" "\tXOR\t-1,R4\n" "\tXOR\t-1,R5\n" "\tXOR\t-1,R6\n" "\tXOR\t-1,R7\n" "\tXOR\t-1,R8\n" "\tXOR\t-1,R9\n" "\tXOR\t-1,R10\n" "\tXOR\t-1,R11\n" "\tXOR\t-1,R12\n" "\tLOD\t(SP),R0\n" "\tLOD\t1(SP),R1\n" "\tLOD\t2(SP),R2\n" "\tLOD\t3(SP),R3\n" "\tLOD\t4(SP),R4\n" "\tLOD\t5(SP),R5\n" "\tLOD\t6(SP),R6\n" "\tLOD\t7(SP),R7\n" "\tLOD\t8(SP),R8\n" "\tLOD\t9(SP),R9\n" "\tLOD\t10(SP),R10\n" "\tLOD\t11(SP),R11\n" "\tLOD\t12(SP),R12\n" "\tADD\t13,SP\n" "\tJMP\tR0\n"); //mpy_test void mpy_test(void); asm("\t.text\n\t.global\tmpy_test\n" "\t.type\tmpy_test,@function\n" "mpy_test:\n" "\tCLR\tR1\n" // First test: let's count multiples of 137 "\tLDI\t137,R2\n" // What we're doing multiples of "\tCLR\tR3\n" // Our accumulator via addition "\tCLR\tR4\n" // Our index for multiplication "mpy_137_test_loop:\n" "\tMOV\tR2,R5\n" "\tMPY\tR4,R5\n" "\tCMP\tR3,R5\n" "\tBNZ\tend_mpy_137_test_loop_failed\n" // Let's try negative while we are at it "\tMOV\tR2,R6\n" "\tNEG\tR6\n" "\tMPY\tR4,R6\n" "\tNEG\tR6\n" "\tCMP\tR3,R6\n" "\tBNZ\tend_mpy_137_test_loop_failed\n" "\tCLR\tR6\n" "\tTEST\t0xffff0000,R3\n" "\tBNZ\tend_mpy_137_test_loop\n" "\tADD\tR2,R3\n" "\tADD\t1,R4\n" "\tBRA\tmpy_137_test_loop\n" "end_mpy_137_test_loop_failed:\n" "\tOR\t1,R1\n" "end_mpy_137_test_loop:\n" // Second test ... whatever that might be "\tJMP\tR0\n"); unsigned soft_mpyuhi(unsigned, unsigned); int soft_mpyshi(int,int); unsigned hard_mpyuhi(unsigned, unsigned); asm("\t.text\n\t.global\thard_mpyuhi\n" "\t.type\thard_mpyuhi,@function\n" "hard_mpyuhi:\n" "\tNOOP\n" "\tNOOP\n" "\tMPYUHI\tR2,R1\n" "\tRETN\n"); int hard_mpyshi(int, int); asm("\t.text\n\t.global\thard_mpyshi\n" "\t.type\thard_mpyshi,@function\n" "hard_mpyshi:\n" "\tMPYSHI\tR2,R1\n" "\tRETN\n"); void debugmpy(char *str, int a, int b, int s, int r) { #ifdef HAVE_SCOPE // Trigger the scope, if it hasn't been triggered yet // but ... dont reset it if it has been. *SCOPE = TRIGGER_SCOPE_NOW; #endif txstr("\r\n"); txstr(str); txhex(a); txstr(" x "); txhex(b); txstr(" = "); txhex(s); txstr("(Soft) = "); txhex(r); txstr("(Hard)\r\n"); } int mpyhi_test(void) { int a = 0xf97e27ab, b = 0; while(b<0x6fffffff) { int r, sr; sr = soft_mpyuhi(a, b); r = hard_mpyuhi(a,b); if (r != sr) { debugmpy("MPYUHI: ", a,b,sr,r); return 1; } sr = soft_mpyshi(a, b); r = hard_mpyshi(a,b); if (r != sr) { debugmpy("MPYSHI: ", a,b,sr,r); return 2; } sr = soft_mpyshi(-a, b); r = hard_mpyshi(-a,b); if (r != sr) { debugmpy("MPYSHI-NEG: ", -a,b,sr,r); return 3; } b += 0x197e2*7; } return 0; } unsigned soft_mpyuhi(unsigned a, unsigned b) { unsigned alo, ahi, blo, bhi; unsigned rhi, rlhi, rllo; alo = (a & 0x0ffff); ahi = (a>>16)& 0x0ffff; blo = (b & 0x0ffff); bhi = (b>>16)& 0x0ffff; rhi = 0; rlhi = 0; rllo = 0; for(int i=0; i<16; i++) { if (b&(1<<i)) { unsigned slo, shi, sup; slo = (alo << i); shi = (ahi << i); shi |= (slo>>16) & 0x0ffff; slo &= 0x0ffff; sup = (shi>>16)&0x0ffff; shi &= 0x0ffff; rhi += sup; rlhi += shi; rllo += slo; rlhi += (rllo >> 16)&0x0ffff; rllo &= 0x0ffff; rhi += (rlhi >> 16)&0x0ffff; rlhi &= 0x0ffff; } } for(int i=16; i<32; i++) { if (b&(1<<i)) { unsigned slo, shi, sup; slo = (alo << (i-16)); shi = (ahi << (i-16)); shi |= (slo>>16) & 0x0ffff; slo &= 0x0ffff; sup = (shi>>16)&0x0ffff; shi &= 0x0ffff; rhi += sup << 16; rhi += shi; rlhi += slo; rhi += (rlhi >> 16)&0x0ffff; rlhi &= 0x0ffff; } } return rhi; } int soft_mpyshi(int a, int b) { unsigned sgn, r, p; sgn = ((a^b)>>31)&0x01; if (a<0) a = -a; if (b<0) b = -b; p = a * b; // This will only fail if the lower 32-bits of of a*b are 0, // at which point our following negation won't capture the carry it // needs. r = soft_mpyuhi(a, b); r = (sgn)?(r^-1):r; if ((sgn)&&(p==0)) r += 1; return r; } //brev_test //pipeline_test -- used to be called pipeline memory race conditions void pipeline_test(void); asm("\t.text\n\t.global\tpipeline_test\n" "\t.type\tpipeline_test,@function\n" "pipeline_test:\n" "\tSUB\t2,SP\n" // Test setup "\tLDI\t275,R2\n" "\tSTO\tR2,1(SP)\n" "\tMOV\t1(SP),R2\n" "\tSTO\tR2,(SP)\n" "\tCLR\tR2\n" // "\tMOV\tSP,R2\n" "\tLOD\t(R2),R2\n" "\tLOD\t(R2),R2\n" "\tCMP\t275,R2\n" "\tOR.NZ\t1,R1\n" // "\tMOV\tSP,R2\n" // Here's the test sequence "\tLOD\t(R2),R3\n" "\tLOD\t1(R2),R4\n" "\tSTO\tR4,1(R3)\n" // Make sure we clear the load pipeline "\tLOD\t(R2),R3\n" // Load our written value "\tLOD\t2(R2),R4\n" "\tCMP\t275,R4\n" "\tOR.NZ\t2,R1\n" // // // Next (once upon a time) failing sequence: // LOD -x(R12),R0 // LOD y(R0),R0 "\tMOV\tSP,R2\n" "\tMOV\t1(R2),R3\n" "\tSTO\tR3,1(R2)\n" "\tLDI\t3588,R4\n" // Just some random value "\tSTO\tR4,2(R2)\n" "\tMOV\tR2,R3\n" // Here's the test sequence "\tLOD\t(R2),R3\n" "\tLOD\t1(R3),R3\n" "\tCMP\tR4,R3\n" "\tOR.NZ\t4,R1\n" // "\tADD\t2,SP\n" "\tJMP\tR0\n"); //mempipe_test void mempipe_test(void); asm("\t.text\n\t.global\tmempipe_test\n" "\t.type\tmempipe_test,@function\n" "mempipe_test:\n" "\tSUB\t4,SP\n" "\tSTO\tR0,(SP)\n" "\tLDI\t0x1000,R11\n" // Test #1 ... Let's start by writing a value to memory "\tLDI\t-1,R2\n" "\tCLR\tR3\n" "\tSTO\tR2,2(SP)\n" "\tLOD\t2(SP),R3\n" "\tCMP\tR3,R2\n" "\tOR.NZ\t1,R1\n" // Test #2, reading and then writing a value from memory "\tNOOP\n" "\tNOOP\n" "\tCLR\tR2\n" "\tCLR\tR3\n" "\tLOD\t2(SP),R2\n" // This should load back up our -1 value "\tSTO\tR2,3(SP)\n" // Insist that the pipeline clear "\tLOD\t2(SP),R2\n" // Now let's try loading into R3 "\tNOOP\n" "\tNOOP\n" "\tNOOP\n" "\tNOOP\n" "\tLOD\t3(SP),R3\n" "\tCMP\tR3,R2\n" "\tOR.NZ\t2,R1\n" // "\tLOD\t(SP),R0\n" "\tADD\t4,SP\n" "\tJMP\tR0\n"); //cexec_test void cexec_test(void); asm("\t.text\n\t.global\tcexec_test\n" "\t.type\tcexec_test,@function\n" "cexec_test:\n" "\tSUB\t1,SP\n" "\tSTO\tR0,(SP)\n" // "\tXOR\tR2,R2\n" "\tADD.Z\t1,R2\n" "\tADD.NZ\t1,R1\n" "\tCMP.Z\t0,R2\n" "\tOR.Z\t2,R1\n" // "\tLOD\t(SP),R0\n" "\tADD\t1,SP\n" "\tJMP\tR0\n"); // Pipeline stalls have been hideous problems for me. The CPU has been modified // with special logic to keep stages from stalling. For the most part, this // means that ALU and memory results may be accessed either before or as they // are written to the register file. This set of code is designed to test // whether this bypass logic works. // //nowaitpipe_test void nowaitpipe_test(void); asm("\t.text\n\t.global\tnowaitpipe_test\n" "\t.type\tnowaitpipe_test,@function\n" "nowaitpipe_test:\n" "\tSUB\t2,SP\n" // // Let's start with ALU-ALU testing // AA: result->input A "\tLDI\t-1,R2\n" "\tCLR\tR2\n" "\tADD\t1,R2\n" "\tCMP\t1,R2\n" "\tOR.NZ\t1,R1\n" // // AA: result -> input B "\tCLR\tR2\n" "\tCLR\tR3\n" "\tADD\t1,R2\n" "\tCMP\tR2,R3\n" "\tOR.Z\t2,R1\n" // AA: result -> input A on condition "\tXOR\tR2,R2\n" "\tADD.Z\t5,R2\n" "\tCMP\t5,R2\n" "\tOR.NZ\t4,R1\n" // AA: result -> input B on condition "\tCLR\tR2\n" "\tXOR\tR3,R3\n" "\tADD.Z\t5,R2\n" "\tCMP\tR2,R3\n" "\tOR.Z\t8,R1\n" // AA: result->input B plus offset "\tCLR\tR2\n" "\tXOR\tR3,R3\n" "\tADD\t5,R2\n" "\tCMP\t-5(R2),R3\n" "\tOR.NZ\t16,R1\n" // AA: result->input B plus offset on condition "\tCLR\tR2\n" "\tXOR\tR3,R3\n" "\tADD.Z\t5,R2\n" "\tCMP\t-5(R2),R3\n" "\tOR.NZ\t32,R1\n" // // Then we need to do the ALU-MEM input testing // "\tCLR\tR2\n" "\tSTO\tR2,1(SP)\n" "\tLDI\t8352,R2\n" "\tLOD\t1(SP),R2\n" "\tTST\t-1,R2\n" "\tOR.NZ\t64,R1\n" // Let's try again, this time with something that's not zero "\tLDI\t937,R2\n" "\tSTO\tR2,1(SP)\n" "\tNOOP\n" "\tLOD\t1(SP),R2\n" "\tCMP\t938,R2\n" "\tOR.GE\t128,R1\n" "\tCMP\t936,R2\n" "\tOR.LT\t256,R1\n" // Mem output->ALU input testing // Okay, we just did that as part of our last test // Mem output->mem input testing "\tLDI\t5328,R2\n" "\tLOD\t1(SP),R2\n" "\tSTO\tR2,1(SP)\n" "\tLOD\t1(SP),R3\n" "\tCMP\t937,R3\n" "\tOR.NZ\t512,R1\n" // "\tADD\t2,SP\n" "\tJMP\tR0\n"); //bcmem_test void bcmem_test(void); asm("\t.text\n.global\tbcmem_test\n" "\t.type\tbcmem_test,@function\n" "bcmem_test:\n" "\tSUB\t1,SP\n" "\tCLR\tR1\n" "\tCLR\tR2\n" "\tLDI\t-1,R3\n" "\tLDI\t0x13000,R4\n" "\tSTO\tR2,(SP)\n" "\tLOD\t(SP),R3\n" "\tCMP\tR2,R3\n" "\tOR.NZ\t1,R1\n" "\tCMP\t0x13000,R4\n" "\tBZ\tbcmem_test_cmploc_1\n" "\tSTO\tR4,(SP)\n" "bcmem_test_cmploc_1:\n" "\tLOD\t(SP),R2\n" "\tCMP\tR2,R4\n" "\tOR.Z\t2,R1\n" "\tCLR\tR2\n" "\tCMP\tR2,R4\n" "\tBZ\tbcmem_test_cmploc_2\n" "\tSTO.NZ\tR4,(SP)\n" "bcmem_test_cmploc_2:\n" "\tLOD\t(SP),R2\n" "\tCMP\tR2,R4\n" "\tOR.NZ\t4,R1\n" // "\tADD\t1,SP\n" "\tJMP\tR0\n"); // The illegal instruction test. Specifically, illegal instructions cannot be // allowed to execute. The PC must, upon completion, point to the illegal // instruction that caused the exception. // // To create our illegal instruction, we assume that the only the three // operations without arguments are NOOP, BREAK, LOCK, and so we envision a // fourth instruction to create. void ill_test(void); asm("\t.text\n.global\till_test\n" "\t.type\till_test,@function\n" "ill_test:\n" "\t.word\t0x7ff00000\n" "\tJMP\tR0\n"); // // The CC register has some ... unique requirements associated with it. // Particularly, flags are unavailable until after an ALU operation completes, // and they can't really be bypassed for the CC register. After writeback, // the "new" CC register isn't really available for another clock. Trying to // bypass this extra clock can have problems, since ... some bits are fixed, // some bits can only be changed by the supervisor, and others can/will change // and even have effects--like sending the CPU to supervisor mode or // alternatively to sleep. // // Here, let's see if our pipeline can successfully navigate any of these // issues. // void ccreg_test(void); asm("\t.text\n.global\tccreg_test\n" "\t.type\tccreg_test,@function\n" "ccreg_test:\n" // First test: If we try to change the fixed bits, will they change // because the pipeline tries to bypass the write-back stage "\tCLR\tR1\n" // We'll start with an "answer" of success "\tMOV\tCC,R2\n" "\tMOV\tR2,R4\n" // Keep a copy "\tBREV\t0x0ff,R3\n" // Attempt to change the top fixed bits "\tXOR\tR3,R2\n" // Arrange for the changes "\tMOV\tR2,CC\n" // Set the changes (they won't take) "\tMOV\tCC,R5\n" // See if the pipeline makes them take "\tXOR\tR4,R5\n" // Let's look for anything that has changed "\tOR.NZ\t1,R1\n" // If anything has changed, then we fail the tst // // Test #2: Can we set the flags? "\tMOV\tCC,R6\n" "\tOR\t15,R6\n" "\tMOV\tR6,CC\n" "\tMOV\tCC,R7\n" "\tAND\t15,R7\n" "\tCMP\t15,R7\n" "\tOR.NZ\t2,R1\n" // // Test #3: How about setting specific flags, and immediately acting // on them? "\tXOR\t1+R8,R8\n" // Turn off the Z bit "\tOR\t1,CC\n" // Turn on the Z bit "\tOR.NZ\t4,R1\n" // // Test #4: Can we load the CC plus a value into a register? // I don't think so ... "\tJMP\tR0\n"); // Multiple argument test __attribute__((noinline)) int multiarg_subroutine(int a, int b, int c, int d, int e, int f, int g) { if (a!=0) return 1; if (b!=1) return 2; if (c!=2) return 4; if (d!=3) return 8; if (e!=4) return 16; if (f!=5) return 32; if (g!=6) return 64; return 0; } int multiarg_test(void) { return multiarg_subroutine(0,1,2,3,4,5,6); } __attribute__((noinline)) void wait(unsigned int msk) { *PIC = 0x7fff0000|msk; asm("MOV\tidle_task(PC),uPC\n"); *PIC = 0x80000000|(msk<<16); asm("WAIT\n"); *PIC = 0; // Turn interrupts back off, lest they confuse the test } asm("\n\t.text\nidle_task:\n\tWAIT\n\tBRA\tidle_task\n"); __attribute__((noinline)) void txchr(char v) { if (zip_cc() & CC_GIE) { while(*UARTTX & 0x100) ; } else wait(INT_UARTTX); *UARTTX = v; } __attribute__((noinline)) void txstr(const char *str) { const char *ptr = str; while(*ptr) txchr(*ptr++); } __attribute__((noinline)) void txhex(int num) { for(int ds=28; ds>=0; ds-=4) { int ch; ch = (num >> ds)&0x0f; if (ch >= 10) ch = 'A'+ch-10; else ch += '0'; txchr(ch); } } __attribute__((noinline)) void tx4hex(int num) { if (num & 0xffff0000){ txhex(num); return; } for(int ds=12; ds>=0; ds-=4) { int ch; ch = (num >> ds)&0x0f; if (ch >= 10) ch = 'A'+ch-10; else ch += '0'; txchr(ch); } } __attribute__((noinline)) void txreg(const char *name, int val) { txstr(name); // 4 characters txstr("0x"); // 2 characters txhex(val); // 8 characters txstr(" "); // 4 characters } __attribute__((noinline)) void save_context(int *context) { zip_save_context(context); } __attribute__((noinline)) void test_fails(int start_time, int *listno) { int context[16], stop_time; // Trigger the scope, if it hasn't already triggered. Otherwise, // if it has triggered, don't clear it. #ifdef HAVE_SCOPE *SCOPE = TRIGGER_SCOPE_NOW; #endif MARKSTOP; save_context(context); *listno++ = context[1]; *listno++ = context[14]; *listno++ = context[15]; #ifdef HAVE_COUNTER *listno = stop_time; #endif txstr("FAIL!\r\n\r\n"); txstr("Between 0x"); txhex(start_time); txstr(" and 0x"); txhex(stop_time); txstr("\r\n\r\n"); txstr("Core-dump:\r\n"); txreg("uR0 : ", context[0]); txreg("uR1 : ", context[1]); txreg("uR2 : ", context[2]); txreg("uR3 : ", context[3]); txstr("\r\n"); txreg("uR4 : ", context[4]); txreg("uR5 : ", context[5]); txreg("uR6 : ", context[6]); txreg("uR7 : ", context[7]); txstr("\r\n"); txreg("uR8 : ", context[8]); txreg("uR9 : ", context[9]); txreg("uR10: ", context[10]); txreg("uR11: ", context[11]); txstr("\r\n"); txreg("uR12: ", context[12]); txreg("uSP : ", context[13]); txreg("uCC : ", context[14]); txreg("uPC : ", context[15]); txstr("\r\n\r\n"); // While previous versions of cputest.c called zip_busy(), here we // reject that notion for the simple reason that zip_busy may not // necessarily halt any Verilator simulation. Instead, we try to // halt the CPU. while(1) zip_halt(); } void testid(const char *str) { const int WIDTH=32; txstr(str); const char *ptr = str; int i = 0; while(0 != *ptr++) i++; i = WIDTH-i; while(i-- > 0) txchr(' '); } int testlist[32]; void entry(void) { int context[16]; int user_stack[256], *user_stack_ptr = &user_stack[256]; int start_time, i; for(i=0; i<32; i++) testlist[i] = -1; #ifdef HAVE_TIMER *TIMER = 0x7fffffff; #endif #ifdef HAVE_COUNTER *COUNTER = 0; #endif #ifdef HAVE_SCOPE *SCOPE = PREPARE_SCOPE; #endif // *UART_CTRL = 8333; // 9600 Baud, 8-bit chars, no parity, one stop bit *UART_CTRL = 25; // 9600 Baud, 8-bit chars, no parity, one stop bit // txstr("\r\n"); txstr("Running CPU self-test\r\n"); txstr("-----------------------------------\r\n"); int tnum = 0; // Test break instruction in user mode // Make sure the break works as designed testid("Break test #1"); MARKSTART; if ((run_test(break_one, user_stack_ptr))||(zip_ucc()&0x1f50)) test_fails(start_time, &testlist[tnum]); save_context(context); if ((context[15] != (int)break_one+1)||(0==(zip_ucc()&0x80))) test_fails(start_time, &testlist[tnum]); txstr("Pass\r\n"); testlist[tnum++] = 0; // 0 // Test break instruction in user mode // Make sure that a decision on the clock prior won't still cause a // break condition testid("Break test #2"); MARKSTART; if ((run_test(break_two, user_stack_ptr))||(zip_ucc()&0x1d90)) test_fails(start_time, &testlist[tnum]); txstr("Pass\r\n"); testlist[tnum++] = 0; // #1 // LJMP test ... not (yet) written // Test the early branching capability // Does it successfully clear whatever else is in the pipeline? testid("Early Branch test"); MARKSTART; if ((run_test(early_branch_test, user_stack_ptr))||(zip_ucc()&0x01d90)) test_fails(start_time, &testlist[tnum]); txstr("Pass\r\n"); testlist[tnum++] = 0; // #2 // TRAP test testid("Trap test/AND"); MARKSTART; if ((run_test(trap_test_and, user_stack_ptr))||(zip_ucc()&0x01d90)) test_fails(start_time, &testlist[tnum]); if ((zip_ucc() & 0x0200)==0) test_fails(start_time, &testlist[tnum]); txstr("Pass\r\n"); testlist[tnum++] = 0; // #3 testid("Trap test/CLR"); MARKSTART; if ((run_test(trap_test_clr, user_stack_ptr))||(zip_ucc()&0x01d90)) test_fails(start_time, &testlist[tnum]); if ((zip_ucc() & 0x0200)==0) test_fails(start_time, &testlist[tnum]); txstr("Pass\r\n"); testlist[tnum++] = 0; // #4 // Overflow test testid("Overflow test"); MARKSTART; if ((run_test(overflow_test, user_stack_ptr))||(zip_ucc()&0x01d90)) test_fails(start_time, &testlist[tnum]); txstr("Pass\r\n"); testlist[tnum++] = 0; // #5 // Carry test testid("Carry test"); MARKSTART; if ((run_test(carry_test, user_stack_ptr))||(zip_ucc()&0x01d90)) test_fails(start_time, &testlist[tnum]); txstr("Pass\r\n"); testlist[tnum++] = 0; // #6 // LOOP_TEST testid("Loop test"); MARKSTART; if ((run_test(loop_test, user_stack_ptr))||(zip_ucc()&0x01d90)) test_fails(start_time, &testlist[tnum]); txstr("Pass\r\n"); testlist[tnum++] = 0; // #7 -- FAILS // SHIFT_TEST testid("Shift test"); MARKSTART; if ((run_test(shift_test, user_stack_ptr))||(zip_ucc()&0x01d90)) test_fails(start_time, &testlist[tnum]); txstr("Pass\r\n"); testlist[tnum++] = 0; // #8 // BREV_TEST //testid("BREV/stack test"); MARKSTART; //if ((run_test(brev_test, user_stack_ptr))||(zip_ucc()&0x01d90)) //test_fails(start_time); //txstr("Pass\r\n"); // PIPELINE_TEST testid("Pipeline test"); MARKSTART; if ((run_test(pipeline_test, user_stack_ptr))||(zip_ucc()&0x01d90)) test_fails(start_time, &testlist[tnum]); txstr("Pass\r\n"); testlist[tnum++] = 0; // #10 // MEM_PIPELINE_STACK_TEST testid("Mem-Pipeline test"); MARKSTART; if ((run_test(mempipe_test, user_stack_ptr))||(zip_ucc()&0x01d90)) test_fails(start_time, &testlist[tnum]); txstr("Pass\r\n"); testlist[tnum++] = 0; // #11 // CONDITIONAL EXECUTION test testid("Conditional Execution test"); MARKSTART; if ((run_test(cexec_test, user_stack_ptr))||(zip_ucc()&0x01d90)) test_fails(start_time, &testlist[tnum]); txstr("Pass\r\n"); testlist[tnum++] = 0; // #12 -- FAILS // NOWAIT pipeline test testid("No-waiting pipeline test"); MARKSTART; if ((run_test(nowaitpipe_test, user_stack_ptr))||(zip_ucc()&0x01d90)) test_fails(start_time, &testlist[tnum]); txstr("Pass\r\n"); testlist[tnum++] = 0; // #13 // BCMEM test testid("Conditional Branching test"); MARKSTART; if ((run_test(bcmem_test, user_stack_ptr))||(zip_ucc()&0x01d90)) test_fails(start_time, &testlist[tnum]); txstr("Pass\r\n"); testlist[tnum++] = 0; // #14 // Illegal Instruction test testid("Ill Instruction test, NULL PC"); MARKSTART; if ((run_test(NULL, user_stack_ptr))||((zip_ucc()^0x100)&0x01d90)) test_fails(start_time, &testlist[tnum]); txstr("Pass\r\n"); testlist[tnum++] = 0; // Illegal Instruction test testid("Ill Instruction test, two"); MARKSTART; if ((run_test(ill_test, user_stack_ptr))||((zip_ucc()^0x100)&0x01d90)) test_fails(start_time, &testlist[tnum]); save_context(context); if (context[15] != (int)&ill_test) test_fails(start_time, &testlist[tnum]); txstr("Pass\r\n"); testlist[tnum++] = 0; // Pipeline memory race condition test // DIVIDE test // CC Register test testid("CC Register test"); MARKSTART; if ((run_test(ccreg_test, user_stack_ptr))||(zip_ucc()&0x01d90)) test_fails(start_time, &testlist[tnum]); txstr("Pass\r\n"); testlist[tnum++] = 0; // Multiple argument test testid("Multi-Arg test"); MARKSTART; if ((run_test(multiarg_test, user_stack_ptr))||(zip_ucc()&0x01d90)) test_fails(start_time, &testlist[tnum]); txstr("Pass\r\n"); testlist[tnum++] = 0; // MPY_TEST testid("Multiply test"); MARKSTART; if ((run_test(mpy_test, user_stack_ptr))||(zip_ucc()&0x01d90)) test_fails(start_time, &testlist[tnum]); txstr("Pass\r\n"); testlist[tnum++] = 0; // #9 // MPYxHI_TEST testid("Multiply HI-word test"); MARKSTART; if ((run_test(mpyhi_test, user_stack_ptr))||(zip_ucc()&0x01d90)) test_fails(start_time, &testlist[tnum]); txstr("Pass\r\n"); testlist[tnum++] = 0; // #9 txstr("\r\n"); txstr("-----------------------------------\r\n"); txstr("All tests passed. Halting CPU.\r\n"); zip_halt(); } // To build this: // zip-gcc -O3 -Wall -Wextra -nostdlib -fno-builtin -T xula.ld -Wl,-Map,cputest.map cputest.cpp -o cputest // zip-objdump -D cputest > cputest.txt //
Go to most recent revision | Compare with Previous | Blame | View Log