diff options
| author | kx <kx@radix-linux.su> | 2026-10-09 18:00:15 +0300 |
|---|---|---|
| committer | kx <kx@radix-linux.su> | 2026-10-09 18:00:15 +0300 |
| commit | 065da3509019b15dd0e678072e6be629e5330bef (patch) | |
| tree | 69c934c9e786ac048b4fbe8f4c4c5571661521f4 /tests/tst-instruction.c | |
| parent | d130fe27fa753be02ed76fd9b277aa3c3d928eb9 (diff) | |
| download | libmcpu-0.4.0.tar.xz | |
Version 0.4.00.4.0
Diffstat (limited to 'tests/tst-instruction.c')
| -rw-r--r-- | tests/tst-instruction.c | 237 |
1 files changed, 237 insertions, 0 deletions
diff --git a/tests/tst-instruction.c b/tests/tst-instruction.c new file mode 100644 index 0000000..052fb9c --- /dev/null +++ b/tests/tst-instruction.c @@ -0,0 +1,237 @@ +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <string.h> + +#include <libmcpu.h> + + +static int failures = 0; + +#define CHECK(cond,msg) \ + do { \ + if( !(cond) ) \ + { \ + fprintf( stderr, "FAIL: %s\n", (msg) ); \ + ++failures; \ + } \ + } while( 0 ) + + +static cpu_operand +reg_operand( unsigned int bits, __mpu_uint32_t address ) +{ + cpu_operand operand; + + memset( &operand, 0, sizeof(operand) ); + operand.type = CPU_OPERAND_REGISTER; + operand.value.reg.bits = bits; + operand.value.reg.address = address; + + return( operand ); +} + + +static void +clear_instruction( cpu_instruction *instruction, cpu_opcode opcode ) +{ + memset( instruction, 0, sizeof(*instruction) ); + instruction->opcode = opcode; +} + + +int +main( void ) +{ + cpu_instruction instruction; + __mpu_uint32_t psw; + + __cpu_init(); + + /* NOP must not change the logical PSW. */ + __cpu_set_psw( PSW_CF_MASK | PSW_INXF_MASK | PSW_TF_MASK | PSW_IF_MASK ); + psw = __cpu_get_psw(); + + clear_instruction( &instruction, CPU_OP_NOP ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "NOP return code" ); + CHECK( __cpu_get_psw() == psw, "NOP preserves PSW" ); + + /* MOV is a raw physical-register copy and accepts overlap. */ + { + __mpu_UNIT_t *src = __cpu_reg_ptr( 128, 0x00100 ); + __mpu_UNIT_t *dst = __cpu_reg_ptr( 128, 0x00110 ); + unsigned int i; + + CHECK( src != 0 && dst != 0, "MOV register pointers" ); + + for( i = 0; i < 16; ++i ) src[i] = (__mpu_UNIT_t)( 0x20 + i ); + memset( dst, 0, 16 ); + + clear_instruction( &instruction, CPU_OP_MOV ); + instruction.operand[0] = reg_operand( 128, 0x00110 ); + instruction.operand[1] = reg_operand( 128, 0x00100 ); + + psw = __cpu_get_psw(); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "MOV return code" ); + CHECK( memcmp( dst, src, 16 ) == 0, "MOV register contents" ); + CHECK( __cpu_get_psw() == psw, "MOV preserves PSW" ); + + /* Overlap must have memmove semantics. */ + for( i = 0; i < 32; ++i ) __cpu_registers[0x00200 + i] = (__mpu_UNIT_t)i; + + clear_instruction( &instruction, CPU_OP_MOV ); + instruction.operand[0] = reg_operand( 128, 0x00210 ); + instruction.operand[1] = reg_operand( 128, 0x00200 ); + + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, + "overlapping MOV return code" ); + for( i = 0; i < 16; ++i ) + CHECK( __cpu_registers[0x00210 + i] == (__mpu_UNIT_t)i, + "overlapping MOV contents" ); + } + + /* CLR clears physical storage and leaves PSW unchanged. */ + { + __mpu_UNIT_t *dst = __cpu_reg_ptr( 256, 0x00300 ); + unsigned int i; + + CHECK( dst != 0, "CLR register pointer" ); + memset( dst, 0xa5, 32 ); + + clear_instruction( &instruction, CPU_OP_CLR ); + instruction.operand[0] = reg_operand( 256, 0x00300 ); + + psw = __cpu_get_psw(); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "CLR return code" ); + for( i = 0; i < 32; ++i ) CHECK( dst[i] == 0, "CLR register contents" ); + CHECK( __cpu_get_psw() == psw, "CLR preserves PSW" ); + } + + /* IADD delegates arithmetic and integer FLAGS to LibMPU. */ + { + __mpu_UNIT_t *dst = __cpu_reg_ptr( 8, 0x00400 ); + __mpu_UNIT_t *a = __cpu_reg_ptr( 8, 0x00401 ); + __mpu_UNIT_t *b = __cpu_reg_ptr( 8, 0x00402 ); + + *a = 0xff; + *b = 0x01; + *dst = 0x5a; + + __cpu_stt(); + __cpu_sti(); + CHECK( __cpu_stmode( 2 ) == 0, "set CPU status before IADD" ); + + clear_instruction( &instruction, CPU_OP_IADD ); + instruction.operand[0] = reg_operand( 8, 0x00400 ); + instruction.operand[1] = reg_operand( 8, 0x00401 ); + instruction.operand[2] = reg_operand( 8, 0x00402 ); + + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "IADD return code" ); + CHECK( *dst == 0x00, "IADD result" ); + CHECK( __mpu_gtc() == 1, "IADD carry flag" ); + CHECK( __mpu_gtz() == 1, "IADD zero flag" ); + CHECK( __cpu_gtt() == 1, "IADD preserves TF" ); + CHECK( __cpu_gti() == 1, "IADD preserves IF" ); + CHECK( __cpu_gtmode() == 2, "IADD preserves MODE" ); + } + + /* IADC consumes LibMPU carry from the preceding operation. */ + { + __mpu_UNIT_t *dst = __cpu_reg_ptr( 8, 0x00403 ); + __mpu_UNIT_t *a = __cpu_reg_ptr( 8, 0x00404 ); + __mpu_UNIT_t *b = __cpu_reg_ptr( 8, 0x00405 ); + + *a = 1; + *b = 2; + *dst = 0; + __mpu_stc(); + + clear_instruction( &instruction, CPU_OP_IADC ); + instruction.operand[0] = reg_operand( 8, 0x00403 ); + instruction.operand[1] = reg_operand( 8, 0x00404 ); + instruction.operand[2] = reg_operand( 8, 0x00405 ); + + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "IADC return code" ); + CHECK( *dst == 4, "IADC result with carry" ); + } + + /* ISUB and ISBB use the same three-register representation. */ + { + __mpu_UNIT_t *dst = __cpu_reg_ptr( 8, 0x00406 ); + __mpu_UNIT_t *a = __cpu_reg_ptr( 8, 0x00407 ); + __mpu_UNIT_t *b = __cpu_reg_ptr( 8, 0x00408 ); + + *a = 9; + *b = 3; + + clear_instruction( &instruction, CPU_OP_ISUB ); + instruction.operand[0] = reg_operand( 8, 0x00406 ); + instruction.operand[1] = reg_operand( 8, 0x00407 ); + instruction.operand[2] = reg_operand( 8, 0x00408 ); + + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "ISUB return code" ); + CHECK( *dst == 6, "ISUB result" ); + + *a = 9; + *b = 3; + __mpu_stc(); + + clear_instruction( &instruction, CPU_OP_ISBB ); + instruction.operand[0] = reg_operand( 8, 0x00406 ); + instruction.operand[1] = reg_operand( 8, 0x00407 ); + instruction.operand[2] = reg_operand( 8, 0x00408 ); + + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "ISBB return code" ); + CHECK( *dst == 5, "ISBB result with borrow" ); + } + + /* Operand validation belongs to the common executor. */ + clear_instruction( &instruction, CPU_OP_MOV ); + instruction.operand[0] = reg_operand( 16, 0x00500 ); + instruction.operand[1] = reg_operand( 32, 0x00504 ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_WIDTH_FAULT, + "MOV width mismatch" ); + + clear_instruction( &instruction, CPU_OP_IADD ); + instruction.operand[0] = reg_operand( 32, 0x00501 ); /* unaligned */ + instruction.operand[1] = reg_operand( 32, 0x00504 ); + instruction.operand[2] = reg_operand( 32, 0x00508 ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_REGISTER_FAULT, + "invalid register operand" ); + + clear_instruction( &instruction, CPU_OP_CLR ); + instruction.operand[0].type = CPU_OPERAND_IMMEDIATE; + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_OPERAND_FAULT, + "unsupported operand type" ); + + clear_instruction( &instruction, (cpu_opcode)0x7fff ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_OPCODE_FAULT, + "unknown opcode" ); + + CHECK( __cpu_exec( (const cpu_instruction *)0 ) == CPU_EXEC_INSTRUCTION_FAULT, + "NULL instruction" ); + + /* HALT sets only the CPU-owned HF part of PSW. */ + __cpu_set_psw( PSW_CF_MASK | PSW_INXF_MASK | PSW_IF_MASK ); + clear_instruction( &instruction, CPU_OP_HALT ); + CHECK( __cpu_exec( &instruction ) == CPU_EXEC_SUCCESS, "HALT return code" ); + psw = __cpu_get_psw(); + CHECK( (psw & PSW_HF_MASK) != 0, "HALT sets HF" ); + CHECK( (psw & PSW_CF_MASK) != 0, "HALT preserves CF" ); + CHECK( (psw & PSW_INXF_MASK) != 0, "HALT preserves INXF" ); + CHECK( (psw & PSW_IF_MASK) != 0, "HALT preserves IF" ); + + if( failures != 0 ) + { + fprintf( stderr, "libcpu instruction regression: %d failure(s)\n", failures ); + return( 1 ); + } + + printf( "libcpu 0.0.3 decoded instruction/executor regression: PASS\n" ); + printf( "opcodes: NOP HALT MOV CLR IADD IADC ISUB ISBB\n" ); + printf( "binary encoding and addressing modes are not defined yet\n" ); + + return( 0 ); +} |
