summaryrefslogtreecommitdiff
path: root/tests/tst-instruction.c
diff options
context:
space:
mode:
authorkx <kx@radix-linux.su>2026-10-09 18:00:15 +0300
committerkx <kx@radix-linux.su>2026-10-09 18:00:15 +0300
commit065da3509019b15dd0e678072e6be629e5330bef (patch)
tree69c934c9e786ac048b4fbe8f4c4c5571661521f4 /tests/tst-instruction.c
parentd130fe27fa753be02ed76fd9b277aa3c3d928eb9 (diff)
downloadlibmcpu-0.4.0.tar.xz
Version 0.4.00.4.0
Diffstat (limited to 'tests/tst-instruction.c')
-rw-r--r--tests/tst-instruction.c237
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 );
+}