Compare commits

...
17 Commits
Author SHA1 Message Date
JosephFerano 51dae3128b Listings 0056, 0057. Improve README 2025-06-14 10:31:35 +07:00
JosephFerano 85a8172923 Add .LOOP instruction, dump memory to disk with cli arg 2025-03-23 08:58:29 +07:00
JosephFerano fafb440891 Listings 54 and 55 2025-03-23 08:58:12 +07:00
JosephFerano df105ea3cf Generalize with perform_load and perform_store, use bit_set for cpu flags 2025-03-22 18:54:36 +07:00
JosephFerano 831a307975 Fix and simplify (delete) all the signage stuff 2025-03-22 15:21:47 +07:00
JosephFerano ea7b65994b Fix IN and OUT instructions decoding 2025-03-22 15:20:48 +07:00
JosephFerano ab2b107e7e Listing 53 2025-03-22 15:09:00 +07:00
JosephFerano da78d875c1 Effective address movs work, but at what cost? 2025-03-21 20:47:42 +07:00
JosephFerano 3ca4059c44 Listing 52 2025-03-21 20:47:34 +07:00
JosephFerano 11ccfe78a9 Handle Memory movs, change displacement from union to enum 2025-03-21 20:11:39 +07:00
JosephFerano 948960e8f5 Listing 51 2025-03-21 20:11:20 +07:00
JosephFerano 0e90e2c23c Handle additional jumps for listing 50 2025-03-21 17:19:48 +07:00
JosephFerano 99fd7fabd7 Add inst decoded type for better modeling, handle jumps correctly with IP register 2025-03-21 16:33:58 +07:00
JosephFerano d0f91f15f8 Listings 48 and 49 2025-03-21 16:33:24 +07:00
JosephFerano 731e5bd45d CF, OF, and PF flags, keep values as i16, update ref test script 2025-03-20 20:22:12 +07:00
JosephFerano 5cf4768b80 Decode then check reference flags, change registers and flags to use enumerated arrays 2025-03-20 16:13:37 +07:00
JosephFerano cd5eada115 Listing 47 2025-03-20 16:09:44 +07:00
32 changed files with 55273 additions and 167 deletions

No files matched your search

+1
View File
@@ -5,3 +5,4 @@
/decoder8086
/performance-aware
/sim8086
/listing-*.data
+94 -6
View File
@@ -1,8 +1,96 @@
#+OPTIONS: toc:nil
* 8086 CPU Emulator
Following along with [[https://www.computerenhance.com/p/table-of-contents][Performance Aware by Computer Enhance]]
* License
This project is licensed under the terms of the MIT license. For more
information, see the included LICENSE file. However, some of this is related to
a paid course so maybe pay for the course if you want to use these files?
* 8086 CPU Simulator
A complete 8086 processor emulator written in Odin, featuring instruction decoding, execution, and cycle-accurate simulation. Implements a comprehensive subset of the Intel 8086 instruction set with proper flag handling, memory management, and register operations.
** Features
- *Complete 8086 instruction decoding* - Supports MOV, arithmetic, jumps, loops, stack operations, and more
- *Cycle-accurate execution* - Proper CPU state management with registers and flags
- *Memory simulation* - 1MB addressable memory space with effective address calculation
- *Comprehensive testing* - 21 assembly test programs with automated validation
- *Reference validation* - Compares execution results against reference CPU states
** Architecture
The simulator is organized into several focused modules:
- =sim8086.odin= - Main entry point and program orchestration
- =types.odin= - Core data structures (CPU state, instructions, operands)
- =instructions.odin= - Complete 8086 instruction set definitions
- =decoding.odin= - Binary instruction decoding and parsing
- =execution.odin= - CPU execution engine with flag calculations
- =printing.odin= - Assembly output formatting
- =testing.odin= - Reference state parsing and validation
** Building and Running
#+BEGIN_SRC bash
# Build the simulator
make
# Decode and print assembly
./sim8086 program.bin instructions
# Execute with register dump
./sim8086 program.bin registers
# Full execution with memory dump
./sim8086 program.bin all dump
#+END_SRC
** Testing
Comprehensive test coverage included:
#+BEGIN_SRC bash
# Run all assembly tests
./test_asm.sh
# Run reference CPU validation tests
./test_ref_cpu.sh
#+END_SRC
Test files cover:
- Basic MOV operations and addressing modes
- Arithmetic and logical operations with proper flag setting
- Jump and loop instructions
- Stack operations (PUSH/POP)
- Complex programs with multiple instruction types
** Technical Implementation
*** Instruction Decoding
- Bit-level parsing of 8086 machine code
- Support for all addressing modes (register, immediate, memory, direct)
- Proper handling of prefixes and multi-byte instructions
*** Execution Engine
- Register file with proper 8/16-bit access patterns
- CPU flags (Zero, Carry, Sign, Overflow, etc.) with accurate calculations
- Effective address computation for memory operations
- Instruction pointer management and control flow
*** Memory Model
- 1MB linear address space
- Little-endian byte ordering
- Memory-mapped I/O simulation
** Supported Instructions
*Data Movement:* MOV, PUSH, POP, XCHG, LEA, LDS, LES
*Arithmetic:* ADD, SUB, MUL, DIV, INC, DEC, CMP, NEG
*Logical:* AND, OR, XOR, NOT, TEST
*Shifts/Rotates:* SHL, SHR, SAR, ROL, ROR, RCL, RCR
*Control Flow:* JMP, JE, JNE, JL, JG, LOOP, CALL, RET
*String Operations:* MOVS, CMPS, SCAS, LODS, STOS
*System:* INT, IRET, HLT, NOP
Built following the [[https://www.computerenhance.com/p/table-of-contents][Performance Aware Programming]] course by Casey Muratori.
** License
This project is licensed under the MIT License - see the LICENSE file for details.
*Note:* Some test files are derived from the Performance Aware Programming course materials. Consider supporting the course if you find this project useful.
+36
View File
@@ -0,0 +1,36 @@
; ========================================================================
;
; (C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
;
; This software is provided 'as-is', without any express or implied
; warranty. In no event will the authors be held liable for any damages
; arising from the use of this software.
;
; Please see https://computerenhance.com for further information
;
; ========================================================================
; ========================================================================
; LISTING 47
; ========================================================================
bits 16
add bx, 30000
add bx, 10000
sub bx, 5000
sub bx, 5000
mov bx, 1
mov cx, 100
add bx, cx
mov dx, 10
sub cx, dx
add bx, 40000
add cx, -90
mov sp, 99
mov bp, 98
cmp bp, sp
+22
View File
@@ -0,0 +1,22 @@
--- test\listing_0047_challenge_flags execution ---
add bx, 30000 ; bx:0x0->0x7530 flags:->P
add bx, 10000 ; bx:0x7530->0x9c40 flags:P->SO
sub bx, 5000 ; bx:0x9c40->0x88b8 flags:SO->PAS
sub bx, 5000 ; bx:0x88b8->0x7530 flags:PAS->PO
mov bx, 1 ; bx:0x7530->0x1
mov cx, 100 ; cx:0x0->0x64
add bx, cx ; bx:0x1->0x65 flags:PO->P
mov dx, 10 ; dx:0x0->0xa
sub cx, dx ; cx:0x64->0x5a flags:P->PA
add bx, 40000 ; bx:0x65->0x9ca5 flags:PA->PS
add cx, -90 ; cx:0x5a->0x0 flags:PS->CPAZ
mov sp, 99 ; sp:0x0->0x63
mov bp, 98 ; bp:0x0->0x62
cmp bp, sp ; flags:CPAZ->CPAS
Final registers:
bx: 0x9ca5 (40101)
dx: 0x000a (10)
sp: 0x0063 (99)
bp: 0x0062 (98)
flags: CPAS
+23
View File
@@ -0,0 +1,23 @@
; ========================================================================
;
; (C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
;
; This software is provided 'as-is', without any express or implied
; warranty. In no event will the authors be held liable for any damages
; arising from the use of this software.
;
; Please see https://computerenhance.com for further information
;
; ========================================================================
; ========================================================================
; LISTING 48
; ========================================================================
bits 16
mov cx, 200
mov bx, cx
add cx, 1000
mov bx, 2000
sub cx, bx
+12
View File
@@ -0,0 +1,12 @@
--- test\listing_0048_ip_register execution ---
mov cx, 200 ; cx:0x0->0xc8 ip:0x0->0x3
mov bx, cx ; bx:0x0->0xc8 ip:0x3->0x5
add cx, 1000 ; cx:0xc8->0x4b0 ip:0x5->0x9 flags:->A
mov bx, 2000 ; bx:0xc8->0x7d0 ip:0x9->0xc
sub cx, bx ; cx:0x4b0->0xfce0 ip:0xc->0xe flags:A->CS
Final registers:
bx: 0x07d0 (2000)
cx: 0xfce0 (64736)
ip: 0x000e (14)
flags: CS
+24
View File
@@ -0,0 +1,24 @@
; ========================================================================
;
; (C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
;
; This software is provided 'as-is', without any express or implied
; warranty. In no event will the authors be held liable for any damages
; arising from the use of this software.
;
; Please see https://computerenhance.com for further information
;
; ========================================================================
; ========================================================================
; LISTING 49
; ========================================================================
bits 16
mov cx, 3
mov bx, 1000
loop_start:
add bx, 10
sub cx, 1
jnz loop_start
+17
View File
@@ -0,0 +1,17 @@
--- test\listing_0049_conditional_jumps execution ---
mov cx, 3 ; cx:0x0->0x3 ip:0x0->0x3
mov bx, 1000 ; bx:0x0->0x3e8 ip:0x3->0x6
add bx, 10 ; bx:0x3e8->0x3f2 ip:0x6->0x9 flags:->A
sub cx, 1 ; cx:0x3->0x2 ip:0x9->0xc flags:A->
jne $-6 ; ip:0xc->0x6
add bx, 10 ; bx:0x3f2->0x3fc ip:0x6->0x9 flags:->P
sub cx, 1 ; cx:0x2->0x1 ip:0x9->0xc flags:P->
jne $-6 ; ip:0xc->0x6
add bx, 10 ; bx:0x3fc->0x406 ip:0x6->0x9 flags:->PA
sub cx, 1 ; cx:0x1->0x0 ip:0x9->0xc flags:PA->PZ
jne $-6 ; ip:0xc->0xe
Final registers:
bx: 0x0406 (1030)
ip: 0x000e (14)
flags: PZ
+38
View File
@@ -0,0 +1,38 @@
; ========================================================================
;
; (C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
;
; This software is provided 'as-is', without any express or implied
; warranty. In no event will the authors be held liable for any damages
; arising from the use of this software.
;
; Please see https://computerenhance.com for further information
;
; ========================================================================
; ========================================================================
; LISTING 50
; ========================================================================
bits 16
mov ax, 10
mov bx, 10
mov cx, 10
label_0:
cmp bx, cx
je label_1
add ax, 1
jp label_2
label_1:
sub bx, 5
jb label_3
label_2:
sub cx, 2
label_3:
loopnz label_0
+37
View File
@@ -0,0 +1,37 @@
--- test\listing_0050_challenge_jumps execution ---
mov ax, 10 ; ax:0x0->0xa ip:0x0->0x3
mov bx, 10 ; bx:0x0->0xa ip:0x3->0x6
mov cx, 10 ; cx:0x0->0xa ip:0x6->0x9
cmp bx, cx ; ip:0x9->0xb flags:->PZ
je $+7 ; ip:0xb->0x12
sub bx, 5 ; bx:0xa->0x5 ip:0x12->0x15 flags:PZ->P
jb $+5 ; ip:0x15->0x17
sub cx, 2 ; cx:0xa->0x8 ip:0x17->0x1a flags:P->
loopnz $-17 ; cx:0x8->0x7 ip:0x1a->0x9
cmp bx, cx ; ip:0x9->0xb flags:->CAS
je $+7 ; ip:0xb->0xd
add ax, 1 ; ax:0xa->0xb ip:0xd->0x10 flags:CAS->
jp $+7 ; ip:0x10->0x12
sub bx, 5 ; bx:0x5->0x0 ip:0x12->0x15 flags:->PZ
jb $+5 ; ip:0x15->0x17
sub cx, 2 ; cx:0x7->0x5 ip:0x17->0x1a flags:PZ->P
loopnz $-17 ; cx:0x5->0x4 ip:0x1a->0x9
cmp bx, cx ; ip:0x9->0xb flags:P->CPAS
je $+7 ; ip:0xb->0xd
add ax, 1 ; ax:0xb->0xc ip:0xd->0x10 flags:CPAS->P
jp $+7 ; ip:0x10->0x17
sub cx, 2 ; cx:0x4->0x2 ip:0x17->0x1a flags:P->
loopnz $-17 ; cx:0x2->0x1 ip:0x1a->0x9
cmp bx, cx ; ip:0x9->0xb flags:->CPAS
je $+7 ; ip:0xb->0xd
add ax, 1 ; ax:0xc->0xd ip:0xd->0x10 flags:CPAS->
jp $+7 ; ip:0x10->0x12
sub bx, 5 ; bx:0x0->0xfffb ip:0x12->0x15 flags:->CAS
jb $+5 ; ip:0x15->0x1a
loopnz $-17 ; cx:0x1->0x0 ip:0x1a->0x1c
Final registers:
ax: 0x000d (13)
bx: 0xfffb (65531)
ip: 0x001c (28)
flags: CAS
+30
View File
@@ -0,0 +1,30 @@
; ========================================================================
;
; (C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
;
; This software is provided 'as-is', without any express or implied
; warranty. In no event will the authors be held liable for any damages
; arising from the use of this software.
;
; Please see https://computerenhance.com for further information
;
; ========================================================================
; ========================================================================
; LISTING 51
; ========================================================================
bits 16
mov word [1000], 1
mov word [1002], 2
mov word [1004], 3
mov word [1006], 4
mov bx, 1000
mov word [bx + 4], 10
mov bx, word [1000]
mov cx, word [1002]
mov dx, word [1004]
mov bp, word [1006]
+18
View File
@@ -0,0 +1,18 @@
--- test\listing_0051_memory_mov execution ---
mov word [+1000], 1 ; ip:0x0->0x6
mov word [+1002], 2 ; ip:0x6->0xc
mov word [+1004], 3 ; ip:0xc->0x12
mov word [+1006], 4 ; ip:0x12->0x18
mov bx, 1000 ; bx:0x0->0x3e8 ip:0x18->0x1b
mov word [bx+4], 10 ; ip:0x1b->0x20
mov bx, [+1000] ; bx:0x3e8->0x1 ip:0x20->0x24
mov cx, [+1002] ; cx:0x0->0x2 ip:0x24->0x28
mov dx, [+1004] ; dx:0x0->0xa ip:0x28->0x2c
mov bp, [+1006] ; bp:0x0->0x4 ip:0x2c->0x30
Final registers:
bx: 0x0001 (1)
cx: 0x0002 (2)
dx: 0x000a (10)
bp: 0x0004 (4)
ip: 0x0030 (48)
+36
View File
@@ -0,0 +1,36 @@
; ========================================================================
;
; (C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
;
; This software is provided 'as-is', without any express or implied
; warranty. In no event will the authors be held liable for any damages
; arising from the use of this software.
;
; Please see https://computerenhance.com for further information
;
; ========================================================================
; ========================================================================
; LISTING 52
; ========================================================================
bits 16
mov dx, 6
mov bp, 1000
mov si, 0
init_loop_start:
mov word [bp + si], si
add si, 2
cmp si, dx
jnz init_loop_start
mov bx, 0
mov si, 0
add_loop_start:
mov cx, word [bp + si]
add bx, cx
add si, 2
cmp si, dx
jnz add_loop_start
+42
View File
@@ -0,0 +1,42 @@
--- test\listing_0052_memory_add_loop execution ---
mov dx, 6 ; dx:0x0->0x6 ip:0x0->0x3
mov bp, 1000 ; bp:0x0->0x3e8 ip:0x3->0x6
mov si, 0 ; ip:0x6->0x9
mov word [bp+si], si ; ip:0x9->0xb
add si, 2 ; si:0x0->0x2 ip:0xb->0xe
cmp si, dx ; ip:0xe->0x10 flags:->CPAS
jne $-7 ; ip:0x10->0x9
mov word [bp+si], si ; ip:0x9->0xb
add si, 2 ; si:0x2->0x4 ip:0xb->0xe flags:CPAS->
cmp si, dx ; ip:0xe->0x10 flags:->CAS
jne $-7 ; ip:0x10->0x9
mov word [bp+si], si ; ip:0x9->0xb
add si, 2 ; si:0x4->0x6 ip:0xb->0xe flags:CAS->P
cmp si, dx ; ip:0xe->0x10 flags:P->PZ
jne $-7 ; ip:0x10->0x12
mov bx, 0 ; ip:0x12->0x15
mov si, 0 ; si:0x6->0x0 ip:0x15->0x18
mov cx, [bp+si] ; ip:0x18->0x1a
add bx, cx ; ip:0x1a->0x1c
add si, 2 ; si:0x0->0x2 ip:0x1c->0x1f flags:PZ->
cmp si, dx ; ip:0x1f->0x21 flags:->CPAS
jne $-9 ; ip:0x21->0x18
mov cx, [bp+si] ; cx:0x0->0x2 ip:0x18->0x1a
add bx, cx ; bx:0x0->0x2 ip:0x1a->0x1c flags:CPAS->
add si, 2 ; si:0x2->0x4 ip:0x1c->0x1f
cmp si, dx ; ip:0x1f->0x21 flags:->CAS
jne $-9 ; ip:0x21->0x18
mov cx, [bp+si] ; cx:0x2->0x4 ip:0x18->0x1a
add bx, cx ; bx:0x2->0x6 ip:0x1a->0x1c flags:CAS->P
add si, 2 ; si:0x4->0x6 ip:0x1c->0x1f
cmp si, dx ; ip:0x1f->0x21 flags:P->PZ
jne $-9 ; ip:0x21->0x23
Final registers:
bx: 0x0006 (6)
cx: 0x0004 (4)
dx: 0x0006 (6)
bp: 0x03e8 (1000)
si: 0x0006 (6)
ip: 0x0023 (35)
flags: PZ
+35
View File
@@ -0,0 +1,35 @@
; ========================================================================
;
; (C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
;
; This software is provided 'as-is', without any express or implied
; warranty. In no event will the authors be held liable for any damages
; arising from the use of this software.
;
; Please see https://computerenhance.com for further information
;
; ========================================================================
; ========================================================================
; LISTING 53
; ========================================================================
bits 16
mov dx, 6
mov bp, 1000
mov si, 0
init_loop_start:
mov word [bp + si], si
add si, 2
cmp si, dx
jnz init_loop_start
mov bx, 0
mov si, dx
sub bp, 2
add_loop_start:
add bx, word [bp + si]
sub si, 2
jnz add_loop_start
+35
View File
@@ -0,0 +1,35 @@
--- test\listing_0053_add_loop_challenge execution ---
mov dx, 6 ; dx:0x0->0x6 ip:0x0->0x3
mov bp, 1000 ; bp:0x0->0x3e8 ip:0x3->0x6
mov si, 0 ; ip:0x6->0x9
mov word [bp+si], si ; ip:0x9->0xb
add si, 2 ; si:0x0->0x2 ip:0xb->0xe
cmp si, dx ; ip:0xe->0x10 flags:->CPAS
jne $-7 ; ip:0x10->0x9
mov word [bp+si], si ; ip:0x9->0xb
add si, 2 ; si:0x2->0x4 ip:0xb->0xe flags:CPAS->
cmp si, dx ; ip:0xe->0x10 flags:->CAS
jne $-7 ; ip:0x10->0x9
mov word [bp+si], si ; ip:0x9->0xb
add si, 2 ; si:0x4->0x6 ip:0xb->0xe flags:CAS->P
cmp si, dx ; ip:0xe->0x10 flags:P->PZ
jne $-7 ; ip:0x10->0x12
mov bx, 0 ; ip:0x12->0x15
mov si, dx ; ip:0x15->0x17
sub bp, 2 ; bp:0x3e8->0x3e6 ip:0x17->0x1a flags:PZ->
add bx, [bp+si] ; bx:0x0->0x4 ip:0x1a->0x1c
sub si, 2 ; si:0x6->0x4 ip:0x1c->0x1f
jne $-5 ; ip:0x1f->0x1a
add bx, [bp+si] ; bx:0x4->0x6 ip:0x1a->0x1c flags:->P
sub si, 2 ; si:0x4->0x2 ip:0x1c->0x1f flags:P->
jne $-5 ; ip:0x1f->0x1a
add bx, [bp+si] ; ip:0x1a->0x1c flags:->P
sub si, 2 ; si:0x2->0x0 ip:0x1c->0x1f flags:P->PZ
jne $-5 ; ip:0x1f->0x21
Final registers:
bx: 0x0006 (6)
dx: 0x0006 (6)
bp: 0x03e6 (998)
ip: 0x0021 (33)
flags: PZ
+43
View File
@@ -0,0 +1,43 @@
; ========================================================================
;
; (C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
;
; This software is provided 'as-is', without any express or implied
; warranty. In no event will the authors be held liable for any damages
; arising from the use of this software.
;
; Please see https://computerenhance.com for further information
;
; ========================================================================
; ========================================================================
; LISTING 54
; ========================================================================
bits 16
; Start image after one row, to avoid overwriting our code!
mov bp, 64*4
mov dx, 0
y_loop_start:
mov cx, 0
x_loop_start:
; Fill pixel
mov word [bp + 0], cx ; Red
mov word [bp + 2], dx ; Blue
mov byte [bp + 3], 255 ; Alpha
; Advance pixel location
add bp, 4
; Advance X coordinate and loop
add cx, 1
cmp cx, 64
jnz x_loop_start
; Advance Y coordinate and loop
add dx, 1
cmp dx, 64
jnz y_loop_start
File diff suppressed because it is too large. Load diff
+53
View File
@@ -0,0 +1,53 @@
; ========================================================================
;
; (C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
;
; This software is provided 'as-is', without any express or implied
; warranty. In no event will the authors be held liable for any damages
; arising from the use of this software.
;
; Please see https://computerenhance.com for further information
;
; ========================================================================
; ========================================================================
; LISTING 55
; ========================================================================
bits 16
; Start image after one row, to avoid overwriting our code!
mov bp, 64*4
; Draw the solid rectangle red/blue/alpha
mov dx, 64
y_loop_start:
mov cx, 64
x_loop_start:
mov byte [bp + 0], cl ; Red
mov byte [bp + 1], 0 ; Green
mov byte [bp + 2], dl ; Blue
mov byte [bp + 3], 255 ; Alpha
add bp, 4
loop x_loop_start
sub dx, 1
jnz y_loop_start
; Add the line rectangle green
mov bp, 64*4 + 4*64 + 4
mov bx, bp
mov cx, 62
outline_loop_start:
mov byte [bp + 1], 255 ; Top line
mov byte [bp + 61*64*4 + 1], 255 ; Bottom line
mov byte [bx + 1], 255 ; Left line
mov byte [bx + 61*4 + 1], 255 ; Right line
add bp, 4
add bx, 4*64
loop outline_loop_start
File diff suppressed because it is too large. Load diff
+41
View File
@@ -0,0 +1,41 @@
; ========================================================================
;
; (C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
;
; This software is provided 'as-is', without any express or implied
; warranty. In no event will the authors be held liable for any damages
; arising from the use of this software.
;
; Please see https://computerenhance.com for further information
;
; ========================================================================
; ========================================================================
; LISTING 56
; ========================================================================
bits 16
mov bx, 1000
mov bp, 2000
mov si, 3000
mov di, 4000
mov cx, bx
mov dx, 12
mov dx, [1000]
mov cx, [bx]
mov cx, [bp]
mov [si], cx
mov [di], cx
mov cx, [bx + 1000]
mov cx, [bp + 1000]
mov [si + 1000], cx
mov [di + 1000], cx
add cx, dx
add [di + 1000], cx
add dx, 50
+72
View File
@@ -0,0 +1,72 @@
**************
**** 8086 ****
**************
WARNING: Clocks reported by this utility are strictly from the 8086 manual.
They will be inaccurate, both because the manual clocks are estimates, and because
some of the entries in the manual look highly suspicious and are probably typos.
--- test\listing_0056_estimating_cycles execution ---
mov bx, 1000 ; Clocks: +4 = 4 | bx:0x0->0x3e8 ip:0x0->0x3
mov bp, 2000 ; Clocks: +4 = 8 | bp:0x0->0x7d0 ip:0x3->0x6
mov si, 3000 ; Clocks: +4 = 12 | si:0x0->0xbb8 ip:0x6->0x9
mov di, 4000 ; Clocks: +4 = 16 | di:0x0->0xfa0 ip:0x9->0xc
mov cx, bx ; Clocks: +2 = 18 | cx:0x0->0x3e8 ip:0xc->0xe
mov dx, 12 ; Clocks: +4 = 22 | dx:0x0->0xc ip:0xe->0x11
mov dx, [+1000] ; Clocks: +14 = 36 (8 + 6ea) | dx:0xc->0x0 ip:0x11->0x15
mov cx, [bx] ; Clocks: +13 = 49 (8 + 5ea) | cx:0x3e8->0x0 ip:0x15->0x17
mov cx, [bp] ; Clocks: +13 = 62 (8 + 5ea) | ip:0x17->0x1a
mov word [si], cx ; Clocks: +14 = 76 (9 + 5ea) | ip:0x1a->0x1c
mov word [di], cx ; Clocks: +14 = 90 (9 + 5ea) | ip:0x1c->0x1e
mov cx, [bx+1000] ; Clocks: +17 = 107 (8 + 9ea) | ip:0x1e->0x22
mov cx, [bp+1000] ; Clocks: +17 = 124 (8 + 9ea) | ip:0x22->0x26
mov word [si+1000], cx ; Clocks: +18 = 142 (9 + 9ea) | ip:0x26->0x2a
mov word [di+1000], cx ; Clocks: +18 = 160 (9 + 9ea) | ip:0x2a->0x2e
add cx, dx ; Clocks: +3 = 163 | ip:0x2e->0x30 flags:->PZ
add word [di+1000], cx ; Clocks: +25 = 188 (16 + 9ea) | ip:0x30->0x34
add dx, 50 ; Clocks: +4 = 192 | dx:0x0->0x32 ip:0x34->0x37 flags:PZ->
Final registers:
bx: 0x03e8 (1000)
dx: 0x0032 (50)
bp: 0x07d0 (2000)
si: 0x0bb8 (3000)
di: 0x0fa0 (4000)
ip: 0x0037 (55)
**************
**** 8088 ****
**************
WARNING: Clocks reported by this utility are strictly from the 8086 manual.
They will be inaccurate, both because the manual clocks are estimates, and because
some of the entries in the manual look highly suspicious and are probably typos.
--- test\listing_0056_estimating_cycles execution ---
mov bx, 1000 ; Clocks: +4 = 4 | bx:0x0->0x3e8 ip:0x0->0x3
mov bp, 2000 ; Clocks: +4 = 8 | bp:0x0->0x7d0 ip:0x3->0x6
mov si, 3000 ; Clocks: +4 = 12 | si:0x0->0xbb8 ip:0x6->0x9
mov di, 4000 ; Clocks: +4 = 16 | di:0x0->0xfa0 ip:0x9->0xc
mov cx, bx ; Clocks: +2 = 18 | cx:0x0->0x3e8 ip:0xc->0xe
mov dx, 12 ; Clocks: +4 = 22 | dx:0x0->0xc ip:0xe->0x11
mov dx, [+1000] ; Clocks: +18 = 40 (8 + 6ea + 4p) | dx:0xc->0x0 ip:0x11->0x15
mov cx, [bx] ; Clocks: +17 = 57 (8 + 5ea + 4p) | cx:0x3e8->0x0 ip:0x15->0x17
mov cx, [bp] ; Clocks: +17 = 74 (8 + 5ea + 4p) | ip:0x17->0x1a
mov word [si], cx ; Clocks: +18 = 92 (9 + 5ea + 4p) | ip:0x1a->0x1c
mov word [di], cx ; Clocks: +18 = 110 (9 + 5ea + 4p) | ip:0x1c->0x1e
mov cx, [bx+1000] ; Clocks: +21 = 131 (8 + 9ea + 4p) | ip:0x1e->0x22
mov cx, [bp+1000] ; Clocks: +21 = 152 (8 + 9ea + 4p) | ip:0x22->0x26
mov word [si+1000], cx ; Clocks: +22 = 174 (9 + 9ea + 4p) | ip:0x26->0x2a
mov word [di+1000], cx ; Clocks: +22 = 196 (9 + 9ea + 4p) | ip:0x2a->0x2e
add cx, dx ; Clocks: +3 = 199 | ip:0x2e->0x30 flags:->PZ
add word [di+1000], cx ; Clocks: +33 = 232 (16 + 9ea + 8p) | ip:0x30->0x34
add dx, 50 ; Clocks: +4 = 236 | dx:0x0->0x32 ip:0x34->0x37 flags:PZ->
Final registers:
bx: 0x03e8 (1000)
dx: 0x0032 (50)
bp: 0x07d0 (2000)
si: 0x0bb8 (3000)
di: 0x0fa0 (4000)
ip: 0x0037 (55)
+42
View File
@@ -0,0 +1,42 @@
; ========================================================================
;
; (C) Copyright 2023 by Molly Rocket, Inc., All Rights Reserved.
;
; This software is provided 'as-is', without any express or implied
; warranty. In no event will the authors be held liable for any damages
; arising from the use of this software.
;
; Please see https://computerenhance.com for further information
;
; ========================================================================
; ========================================================================
; LISTING 57
; ========================================================================
bits 16
mov bx, 1000
mov bp, 2000
mov si, 3000
mov di, 4000
mov cx, [bp + di]
mov [bx + si], cx
mov cx, [bp + si]
mov [bx + di], cx
mov cx, [bp + di + 1000]
mov [bx + si + 1000], cx
mov cx, [bp + si + 1000]
mov [bx + di + 1000], cx
add dx, [bp + si + 1000]
add word [bp + si], 76
add dx, [bp + si + 1001]
add [di + 999], dx
add word [bp + si], 75
+71
View File
@@ -0,0 +1,71 @@
**************
**** 8086 ****
**************
WARNING: Clocks reported by this utility are strictly from the 8086 manual.
They will be inaccurate, both because the manual clocks are estimates, and because
some of the entries in the manual look highly suspicious and are probably typos.
--- test\listing_0057_challenge_cycles execution ---
mov bx, 1000 ; Clocks: +4 = 4 | bx:0x0->0x3e8 ip:0x0->0x3
mov bp, 2000 ; Clocks: +4 = 8 | bp:0x0->0x7d0 ip:0x3->0x6
mov si, 3000 ; Clocks: +4 = 12 | si:0x0->0xbb8 ip:0x6->0x9
mov di, 4000 ; Clocks: +4 = 16 | di:0x0->0xfa0 ip:0x9->0xc
mov cx, [bp+di] ; Clocks: +15 = 31 (8 + 7ea) | ip:0xc->0xe
mov word [bx+si], cx ; Clocks: +16 = 47 (9 + 7ea) | ip:0xe->0x10
mov cx, [bp+si] ; Clocks: +16 = 63 (8 + 8ea) | ip:0x10->0x12
mov word [bx+di], cx ; Clocks: +17 = 80 (9 + 8ea) | ip:0x12->0x14
mov cx, [bp+di+1000] ; Clocks: +19 = 99 (8 + 11ea) | ip:0x14->0x18
mov word [bx+si+1000], cx ; Clocks: +20 = 119 (9 + 11ea) | ip:0x18->0x1c
mov cx, [bp+si+1000] ; Clocks: +20 = 139 (8 + 12ea) | ip:0x1c->0x20
mov word [bx+di+1000], cx ; Clocks: +21 = 160 (9 + 12ea) | ip:0x20->0x24
add dx, [bp+si+1000] ; Clocks: +21 = 181 (9 + 12ea) | ip:0x24->0x28 flags:->PZ
add word [bp+si], 76 ; Clocks: +25 = 206 (17 + 8ea) | ip:0x28->0x2b flags:PZ->
add dx, [bp+si+1001] ; Clocks: +25 = 231 (9 + 12ea + 4p) | ip:0x2b->0x2f flags:->PZ
add word [di+999], dx ; Clocks: +33 = 264 (16 + 9ea + 8p) | ip:0x2f->0x33 flags:PZ->P
add word [bp+si], 75 ; Clocks: +25 = 289 (17 + 8ea) | ip:0x33->0x36 flags:P->A
Final registers:
bx: 0x03e8 (1000)
bp: 0x07d0 (2000)
si: 0x0bb8 (3000)
di: 0x0fa0 (4000)
ip: 0x0036 (54)
flags: A
**************
**** 8088 ****
**************
WARNING: Clocks reported by this utility are strictly from the 8086 manual.
They will be inaccurate, both because the manual clocks are estimates, and because
some of the entries in the manual look highly suspicious and are probably typos.
--- test\listing_0057_challenge_cycles execution ---
mov bx, 1000 ; Clocks: +4 = 4 | bx:0x0->0x3e8 ip:0x0->0x3
mov bp, 2000 ; Clocks: +4 = 8 | bp:0x0->0x7d0 ip:0x3->0x6
mov si, 3000 ; Clocks: +4 = 12 | si:0x0->0xbb8 ip:0x6->0x9
mov di, 4000 ; Clocks: +4 = 16 | di:0x0->0xfa0 ip:0x9->0xc
mov cx, [bp+di] ; Clocks: +19 = 35 (8 + 7ea + 4p) | ip:0xc->0xe
mov word [bx+si], cx ; Clocks: +20 = 55 (9 + 7ea + 4p) | ip:0xe->0x10
mov cx, [bp+si] ; Clocks: +20 = 75 (8 + 8ea + 4p) | ip:0x10->0x12
mov word [bx+di], cx ; Clocks: +21 = 96 (9 + 8ea + 4p) | ip:0x12->0x14
mov cx, [bp+di+1000] ; Clocks: +23 = 119 (8 + 11ea + 4p) | ip:0x14->0x18
mov word [bx+si+1000], cx ; Clocks: +24 = 143 (9 + 11ea + 4p) | ip:0x18->0x1c
mov cx, [bp+si+1000] ; Clocks: +24 = 167 (8 + 12ea + 4p) | ip:0x1c->0x20
mov word [bx+di+1000], cx ; Clocks: +25 = 192 (9 + 12ea + 4p) | ip:0x20->0x24
add dx, [bp+si+1000] ; Clocks: +25 = 217 (9 + 12ea + 4p) | ip:0x24->0x28 flags:->PZ
add word [bp+si], 76 ; Clocks: +33 = 250 (17 + 8ea + 8p) | ip:0x28->0x2b flags:PZ->
add dx, [bp+si+1001] ; Clocks: +25 = 275 (9 + 12ea + 4p) | ip:0x2b->0x2f flags:->PZ
add word [di+999], dx ; Clocks: +33 = 308 (16 + 9ea + 8p) | ip:0x2f->0x33 flags:PZ->P
add word [bp+si], 75 ; Clocks: +33 = 341 (17 + 8ea + 8p) | ip:0x33->0x36 flags:P->A
Final registers:
bx: 0x03e8 (1000)
bp: 0x07d0 (2000)
si: 0x0bb8 (3000)
di: 0x0fa0 (4000)
ip: 0x0036 (54)
flags: A
+35 -23
View File
@@ -66,7 +66,9 @@ get_op :: proc(inst: Instruction) -> (Op, bool) {
}
return op, interseg
}
parse_operand :: proc(inst: InstructionInfo, opinfo: OperandInfo, data: []u8, processed: ^int, word: bool, has_segreg: Maybe(RegisterId)) -> Operand {
parse_operand :: proc(inst: InstructionInfo, opinfo: OperandInfo, data: []u8, processed: ^int,
total_bytes_processed: int, word: bool, has_segreg: Maybe(RegisterId)) -> Operand {
operand: Operand = None{}
switch opinfo {
case .None:
@@ -80,11 +82,11 @@ parse_operand :: proc(inst: InstructionInfo, opinfo: OperandInfo, data: []u8, pr
case .SecondByteLast3: reg = data[1] & 0b111
}
if opinfo == .SegmentRegister {
operand = (RegisterId){idx = SEGMENT_REGISTER_START + (int)(reg), access = .Full}
operand = (RegisterId){name = Register(SEGMENT_REGISTER_START + (int)(reg)), access = .Full}
} else if word {
operand = RegisterId { idx = (int)(reg), access = .Full }
operand = RegisterId { name = Register(reg), access = .Full }
} else {
operand = RegisterId { idx = (int)(reg % 4), access = reg < 4 ? .Low : .High }
operand = RegisterId { name = Register(reg % 4), access = reg < 4 ? .Low : .High }
}
case .RegisterMemory:
mod := data[1] >> 6
@@ -97,19 +99,22 @@ parse_operand :: proc(inst: InstructionInfo, opinfo: OperandInfo, data: []u8, pr
op = (DirectAddress)(get_i16(data[2:]))
processed^ += 2
} else {
op = MemoryAddr{ addr_id = rm , displacement = None{} }
op = MemoryAddr{ addr_id = rm }
}
} else if mod == 1 {
op = MemoryAddr{ addr_id = rm , displacement = (i8)(data[2]) }
// NOTE: Per the 8086 manual in order to detect signed displacement, you have
// to sign extend a byte to a word. In Odin like in other languages, first
// cast to an i8, before casting to an i16 and it'll sign extend
op = MemoryAddr{ addr_id = rm , displacement = i16(i8(data[2])) }
processed^ += 1
} else if mod == 2 {
op = MemoryAddr{ addr_id = rm , displacement = get_i16(data[2:]) }
processed^ += 2
} else if mod == 3 {
if word {
op = RegisterId { idx = (int)(rm), access = .Full }
op = RegisterId { name = Register(rm), access = .Full }
} else {
op = RegisterId { idx = (int)(rm % 4), access = rm < 4 ? .Low : .High }
op = RegisterId { name = Register(rm % 4), access = rm < 4 ? .Low : .High }
}
}
operand = op
@@ -119,14 +124,11 @@ parse_operand :: proc(inst: InstructionInfo, opinfo: OperandInfo, data: []u8, pr
if inst.has_sign_extension {
word_signed &&= data[0] & 0b0000_0010 == 0
}
value: u16 = word_signed ? u16(get_i16(data[data_idx:])) : u16(data[data_idx])
operand = Immediate { value = value, size = word_signed ? .Signed16 : .Signed8 }
value: i16 = word_signed ? get_i16(data[data_idx:]) : i16(i8(data[data_idx]))
operand = Immediate { value = value, size = word_signed ? ._16 : ._8 }
processed^ += word_signed ? 2 : 1
case .ImmediateUnsigned:
operand = Immediate { value = u16(data[processed^]), size = .Unsigned8 }
processed^ += 1
case .Accumulator:
operand = RegisterId { idx = 0, access = word ? .Full : .Low }
operand = RegisterId { name = .ax, access = word ? .Full : .Low }
case .DirectAddress:
// operand = DirectAddress { value = get_i16(data[1:]) }
operand = (DirectAddress)(get_i16(data[1:]))
@@ -136,10 +138,17 @@ parse_operand :: proc(inst: InstructionInfo, opinfo: OperandInfo, data: []u8, pr
// NOTE: In order to mimic the label offset, you have to take the value you got and add two
operand = (Jump)((i8)(data[1]) + 2)
case .VariablePort:
operand = RegisterId { idx = (int)(Register.dx), access = .Full }
// NOTE: This isn't documented in the manual, but you can tell if we need to use
// the dx register or an immediate if the 4th LSB bit is set
if (data[0] & 0b1000) >> 3 == 1 {
operand = RegisterId { name = .dx, access = .Full }
} else {
operand = Immediate { value = i16(i8(data[1])), size = ._8 }
processed^ += 1
}
case .ShiftRotate:
v_flag := data[0] & 0b10 != 0
operand = v_flag ? RegisterId { idx = 1, access = .Low } : Immediate { value = 1 }
operand = v_flag ? RegisterId { name = .cx, access = .Low } : Immediate { value = 1 }
case .Repeat:
bits := (data[1] & 0b1110) >> 1
w := (data[1] & 0b1) == 1 ? "w" : "b"
@@ -154,8 +163,8 @@ parse_operand :: proc(inst: InstructionInfo, opinfo: OperandInfo, data: []u8, pr
operand = Repeat(fmt.aprintf("%s%s", rep, w))
processed^ += 1
case .DirectWithinSegment:
value := (int)(get_i16(data[1:])) + CPU.total_bytes_processed + 3
operand = Immediate { value = u16(value), size = .Signed16 }
value := (int)(get_i16(data[1:])) + total_bytes_processed + 3
operand = Immediate { value = i16(value), size = ._16 }
processed^ += 2
case .Intersegment:
operand = Intersegment {
@@ -167,10 +176,11 @@ parse_operand :: proc(inst: InstructionInfo, opinfo: OperandInfo, data: []u8, pr
return operand
}
decode_data :: proc(inst_list: ^[dynamic]Instruction, data: []u8, bytes_to_read: int) {
decode_data :: proc(inst_list: ^[dynamic]Instruction, data: []u8, bytes_to_read: int) -> int {
idx := 0
has_segment: Maybe(RegisterId)
has_lock: bool
total_bytes_processed: int
for idx < bytes_to_read {
instruction: Instruction
processed := 1
@@ -203,7 +213,7 @@ decode_data :: proc(inst_list: ^[dynamic]Instruction, data: []u8, bytes_to_read:
continue
} else if inst.opname == .SEGMENT {
reg := (curr_byte & 0b11000) >> 3
has_segment = RegisterId { idx = int(SEGMENT_REGISTER_START+reg) }
has_segment = RegisterId { name = Register(SEGMENT_REGISTER_START+reg) }
idx += 1
continue
} else if inst.opname == .AAM {
@@ -234,8 +244,8 @@ decode_data :: proc(inst_list: ^[dynamic]Instruction, data: []u8, bytes_to_read:
case .Always16: word = true
}
dst_opr = parse_operand(inst, inst.dst, data[idx:], &processed, word, has_segment)
src_opr = parse_operand(inst, inst.src, data[idx:], &processed, word, has_segment)
dst_opr = parse_operand(inst, inst.dst, data[idx:], &processed, total_bytes_processed, word, has_segment)
src_opr = parse_operand(inst, inst.src, data[idx:], &processed, total_bytes_processed, word, has_segment)
if flip {
src_opr, dst_opr = dst_opr, src_opr
@@ -252,6 +262,7 @@ decode_data :: proc(inst_list: ^[dynamic]Instruction, data: []u8, bytes_to_read:
instruction.info = inst
instruction.has_lock = has_lock
instruction.has_segment = has_segment
instruction.segment_offset += total_bytes_processed
instruction.opname = inst.opname
instruction.opname,instruction.indirect_intersegment = get_op(instruction)
@@ -262,6 +273,7 @@ decode_data :: proc(inst_list: ^[dynamic]Instruction, data: []u8, bytes_to_read:
has_lock = false
has_segment = nil
CPU.total_bytes_processed = idx
total_bytes_processed = idx
}
return total_bytes_processed
}
+129 -38
View File
@@ -5,70 +5,161 @@ import "core:fmt"
import "core:math"
import "core:strings"
import "core:reflect"
import "core:strconv"
get_operand_value :: proc(operand: Operand) -> u16 {
get_ip :: proc(cpu: ^Cpu) -> int { return int(cpu.registers[.ip].full) }
get_effective_address_value :: proc(cpu: ^Cpu, id: u8) -> i16 {
switch id {
case 0: return cpu.registers[.bx].full + cpu.registers[.si].full
case 1: return cpu.registers[.bx].full + cpu.registers[.di].full
case 2: return cpu.registers[.bp].full + cpu.registers[.si].full
case 3: return cpu.registers[.bx].full + cpu.registers[.di].full
case 4: return cpu.registers[.si].full
case 5: return cpu.registers[.di].full
case 6: return cpu.registers[.bp].full
case 7: return cpu.registers[.bx].full
}
return -1
}
perform_load :: proc(cpu: ^Cpu, operand: Operand, is_word: bool) -> i16 {
#partial switch opr in operand {
case Immediate:
return opr.value
return i16(opr.value)
case RegisterId:
reg_val := CPU.registers[opr.idx]
reg_val := cpu.registers[opr.name]
switch opr.access {
case .Low, .High:
return u16(opr.access == .Low ? reg_val.low : reg_val.high)
return i16(opr.access == .Low ? reg_val.low : reg_val.high)
case .Full:
return reg_val.full
return i16(reg_val.full)
}
case DirectAddress:
value: i16
if is_word {
value = i16(u16(cpu.memory[opr+1] << 8) | u16(cpu.memory[opr]))
} else {
value = i16(cpu.memory[opr])
}
return value
case MemoryAddr:
value: i16
idx := get_effective_address_value(cpu, opr.addr_id) + opr.displacement
if is_word {
value = i16(u16(cpu.memory[idx+1] << 8) | u16(cpu.memory[idx]))
} else {
value = i16(cpu.memory[idx])
}
return value
}
return 0
}
set_register_value :: proc(reg: RegisterId, value: u16) {
switch reg.access {
check_flags :: proc(cpu: ^Cpu, flags: bit_set[Flag], dst: i16, src: i16, result: i16, is_add: bool) {
if .ZF in flags do cpu.flags[.ZF] = result == 0
if .SF in flags do cpu.flags[.SF] = (result >> 15) & 0x1 == 1
if .CF in flags {
cpu.flags[.CF] = is_add ? u32(dst) + u32(src) > 0xFFFF : src > dst
}
if .AF in flags {
lhs, rhs := dst & 0xF, src & 0xF
cpu.flags[.AF] = is_add ? lhs + rhs > 15 : lhs < rhs
}
if .PF in flags {
val := result
bit_count := 0
val &= 0x00FF
for val != 0 {
if val & 0x1 == 1 {
bit_count += 1
}
val >>= 1
}
cpu.flags[.PF] = bit_count % 2 == 0
}
}
perform_store :: proc(cpu: ^Cpu, value: i16, operand: Operand, is_word: bool) {
#partial switch opr in operand {
case RegisterId:
switch opr.access {
case .Low:
CPU.registers[reg.idx].low = u8(value)
cpu.registers[opr.name].low = u8(value)
case .High:
CPU.registers[reg.idx].high = u8(value)
cpu.registers[opr.name].high = u8(value)
case .Full:
CPU.registers[reg.idx].full = u16(value)
cpu.registers[opr.name].full = i16(value)
}
case DirectAddress:
cpu.memory[opr] = u8(value)
if is_word do cpu.memory[opr+1] = u8((u16(value) & 0xFF00) >> 8)
case MemoryAddr:
effective_addr_val := get_effective_address_value(cpu, opr.addr_id)
addr := effective_addr_val + opr.displacement
cpu.memory[addr] = u8(value)
if is_word do cpu.memory[addr+1] = u8((u16(value) & 0xFF00) >> 8)
}
}
get_cpu_register_by_name :: proc(cpu: ^Cpu, name: string) -> ^RegisterValue {
reg,_ := reflect.enum_from_name(Register, name)
return &cpu.registers[int(reg)]
}
execute_instruction :: proc(cpu: ^Cpu, inst: Instruction) {
if jmp_offset,ok := inst.src.(Jump); ok {
jump: bool
#partial switch inst.opname {
case .JNZ, .JNE: jump = !cpu.flags[.ZF]
case .JE, .JZ: jump = cpu.flags[.ZF]
case .JP, .JPE: jump = cpu.flags[.PF]
case .JB, .JNAE: jump = cpu.flags[.CF]
check_zero_flag :: proc(value: u16) {
CPU.flags.ZF = value == 0
case .LOOP:
cpu.registers[.cx].full -= 1
jump = cpu.registers[.cx].full != 0
case .LOOPNZ, .LOOPNE:
cpu.registers[.cx].full -= 1
// According to this resource, the loopnz inst does not change any flags
// https://yassinebridi.github.io/asm-docs/8086_instruction_set.html#LOOPNZ
// check_zero_flag(cpu, cpu.registers[.cx].full)
jump = cpu.registers[.cx].full != 0 && !cpu.flags[.ZF]
}
check_sign_flag :: proc(value: u16) {
CPU.flags.SF = value >> 15 == 1
if jump {
cpu.registers[.ip].full += i16(jmp_offset)
cpu.registers[.ip].full -= i16(inst.bytes_read)
}
execute_instruction :: proc(inst: Instruction) {
if reg,ok := inst.dst.(RegisterId); ok {
return
}
status_flags := bit_set[Flag]{.OF, .SF, .ZF, .AF, .PF, .CF}
#partial switch inst.opname {
case .MOV:
src_val := get_operand_value(inst.src)
set_register_value(reg, src_val)
src_val := perform_load(cpu, inst.src, inst.is_word)
perform_store(cpu, src_val, inst.dst, inst.is_word)
case .ADD:
src_val := get_operand_value(inst.src)
val := CPU.registers[reg.idx].full + src_val
set_register_value(reg, val)
check_zero_flag(val)
check_sign_flag(val)
src_val := perform_load(cpu, inst.src, inst.is_word)
dst_val := perform_load(cpu, inst.dst, inst.is_word)
val := dst_val + src_val
perform_store(cpu, val, inst.dst, inst.is_word)
check_flags(cpu, status_flags, dst_val, src_val, val, true)
case .SUB:
src_val := get_operand_value(inst.src)
val := CPU.registers[reg.idx].full - src_val
set_register_value(reg, val)
check_zero_flag(val)
check_sign_flag(val)
src_val := perform_load(cpu, inst.src, inst.is_word)
dst_val := perform_load(cpu, inst.dst, inst.is_word)
val := dst_val - src_val
perform_store(cpu, val, inst.dst, inst.is_word)
check_flags(cpu, status_flags, dst_val, src_val, val, false)
case .CMP:
src_val := get_operand_value(inst.src)
val := CPU.registers[reg.idx].full - src_val
check_zero_flag(val)
check_sign_flag(val)
src_val := perform_load(cpu, inst.src, inst.is_word)
dst_val := perform_load(cpu, inst.dst, inst.is_word)
val := dst_val - src_val
check_flags(cpu, status_flags, dst_val, src_val, val, false)
}
}
execute_instructions :: proc(cpu: ^Cpu, instructions: DecodedInstructions) {
halt := false
for !halt && get_ip(cpu) < instructions.total_bytes_decoded {
inst,ok := instructions.inst_map[get_ip(cpu)]
if !ok {
panic("Something went wrong with a jump, most likely")
}
cpu.registers[.ip].full += i16(inst.bytes_read)
execute_instruction(cpu, inst)
}
}
+6 -8
View File
@@ -76,6 +76,7 @@ Op :: enum {
CALL,
JMP,
JNZ,
JNE,
JNGE,
JE,
JZ,
@@ -90,7 +91,6 @@ Op :: enum {
JBE,
JO,
JS,
JNE,
JNL,
JGE,
JNLE,
@@ -106,6 +106,7 @@ Op :: enum {
LOOP,
LOOPZ,
LOOPNZ,
LOOPNE,
JCXZ,
}
@@ -193,16 +194,13 @@ instructions := [?]InstructionInfo {
dst = .Accumulator, src = .Register,
reg_info = .FirstByteLast3, has_flip = true, word_size = .Always16 },
{ opname = .IN, desc = "", mask = 0b11111110, encoding = 0b11100100,
dst = .Accumulator, src = .ImmediateUnsigned },
dst = .Accumulator, src = .Immediate, word_size = .LastBit },
{ opname = .IN, desc = "", mask = 0b11111110, encoding = 0b11101100,
dst = .Accumulator, src = .VariablePort,
word_size = .LastBit, },
dst = .Accumulator, src = .VariablePort, word_size = .LastBit, },
{ opname = .OUT, desc = "", mask = 0b11111110, encoding = 0b11100110,
dst = .ImmediateUnsigned, src = .Accumulator,
word_size = .LastBit, },
dst = .VariablePort, src = .Accumulator, word_size = .LastBit },
{ opname = .OUT, desc = "", mask = 0b11111110, encoding = 0b11101110,
dst = .VariablePort, src = .Accumulator,
word_size = .LastBit, },
dst = .VariablePort, src = .Accumulator, word_size = .LastBit, },
{ opname = .TEST, desc = "", mask = 0b11111110, encoding = 0b10101000,
dst = .Accumulator, src = .Immediate, word_size = .LastBit },
{ opname = .XLAT, desc = "", mask = 0b11111111, encoding = 0b11010111,},
+5 -41
View File
@@ -39,18 +39,10 @@ calculate_effective_address :: proc(r_m: u8) -> string {
get_memory_string :: proc(memoryAddr: MemoryAddr, has_segment: Maybe(RegisterId)) -> string {
disp: string
switch value in memoryAddr.displacement {
case None:
disp = ""
case Disp8:
value := memoryAddr.displacement
if value != 0 {
disp = fmt.aprintf(" %s %d", value > 0 ? "+" : "-", math.abs(value))
}
case Disp16:
if value != 0 {
disp = fmt.aprintf(" %s %d", value > 0 ? "+" : "-", math.abs(value))
}
}
seg_string: string
if segreg, ok := has_segment.?; ok {
seg_string = fmt.aprintf("%s:", get_register_name(segreg))
@@ -59,28 +51,13 @@ get_memory_string :: proc(memoryAddr: MemoryAddr, has_segment: Maybe(RegisterId)
return text
}
get_displacement_string :: proc(displacement: Displacement) -> string {
disp := ""
#partial switch value in displacement {
case i8:
if value != 0 {
disp = fmt.aprintf(" %s %d", value > 0 ? "+" : "-", math.abs(value))
}
case i16:
if value != 0 {
disp = fmt.aprintf(" %s %d", value > 0 ? "+" : "-", math.abs(value))
}
}
return disp
}
get_register_name :: proc(reg_id: RegisterId) -> string {
low_names := [?]string{"al", "cl", "dl", "bl"}
high_names := [?]string{"ah", "ch", "dh", "bh"}
switch reg_id.access {
case .Full: return reflect.enum_string(Register(reg_id.idx))
case .Low: return low_names[reg_id.idx]
case .High: return high_names[reg_id.idx % 4]
case .Full: return reflect.enum_string(reg_id.name)
case .Low: return low_names[int(reg_id.name)]
case .High: return high_names[int(reg_id.name) % 4]
}
return ""
}
@@ -93,14 +70,7 @@ get_operand_string :: proc(operand: Operand, has_segment: Maybe(RegisterId)) ->
case RegisterId:
string_val = get_register_name(val)
case Immediate:
switch val.size {
case .Signed8:
string_val = fmt.aprintf("%d", i8(val.value))
case .Unsigned8:
string_val = fmt.aprintf("%d", u8(val.value))
case .Signed16:
string_val = fmt.aprintf("%d", i16(val.value))
}
string_val = fmt.aprintf("%d", val.value)
case MemoryAddr:
string_val = get_memory_string(val, has_segment)
case DirectAddress:
@@ -154,15 +124,9 @@ get_instruction_string :: proc(inst_info: InstructionInfo, instruction: Instruct
interseg_string = " far"
}
fmt.sbprintf(&instruction_builder, "%s%s %s%s", opname, interseg_string, size_string, src_str)
} else {
// note: i don't know why this is the case, but only the move has the word/byte
// keyword next to the immediate, but other instructions have it on the memory address
if opname == "mov" {
fmt.sbprintf(&instruction_builder, "%s %s, %s%s", opname, dst_str, size_string, src_str)
} else {
fmt.sbprintf(&instruction_builder, "%s %s%s, %s", opname, size_string, dst_str, src_str)
}
}
// Prepare padding and comment to add debug info
b_len := strings.builder_len(instruction_builder)
+65 -19
View File
@@ -5,10 +5,8 @@ import path "core:path/filepath"
import "core:fmt"
import "core:math"
import "core:strings"
CPU := Cpu {
memory = make([dynamic]u8, 65536),
}
import "core:reflect"
import "core:strconv"
main :: proc() {
f,err := os.open(os.args[1])
@@ -35,32 +33,80 @@ main :: proc() {
instruction_list := make([dynamic]string, 0, 512)
instructions_list := make([dynamic]Instruction, 0, 512)
decode_data(&instructions_list, data[:], bytes_read)
total_bytes := decode_data(&instructions_list, data[:], bytes_read)
inst_map := make(map[int]Instruction)
for inst in instructions_list {
execute_instruction(inst)
inst_map[inst.segment_offset] = inst
}
decoded_insts := DecodedInstructions {
inst_list = instructions_list,
inst_map = inst_map,
total_bytes_decoded = total_bytes
}
cpu := Cpu {
memory = make([dynamic]u8, 1024 * 1024), // 1,048,576
}
num_idx := strings.last_index(os.args[1], "list-") + 5
if listing_num,ok := strconv.parse_int(os.args[1][num_idx:num_idx+4]); ok {
if listing_num > 42 {
execute_instructions(&cpu, decoded_insts)
}
}
if what_to_print == "registers" || what_to_print == "all" {
for reg,i in CPU.registers {
full := fmt.aprintf("%s: %d ", get_register_name(RegisterId{idx=i}), reg.full)
hex := fmt.aprintf("0x%04x ", reg.full)
fmt.println("Registers")
for reg_val,name in cpu.registers {
full := fmt.aprintf("%s: %d ", name, i16(reg_val.full))
hex := fmt.aprintf("0x%04x ", u16(reg_val.full))
fmt.printf("%s %*[1]s %s %*[4]s %08b %08b",
full, 18 - len(full), "|", hex, 10 - len(hex), "|", reg.high, reg.low)
full, 18 - len(full), "|", hex, 10 - len(hex), "|", reg_val.high, reg_val.low)
fmt.println()
}
path,ok := strings.replace(os.args[1], ".bin", ".txt", 1)
expected_cpu,_ := extract_expected_cpu_state(path)
for reg,i in expected_cpu.registers {
if CPU.registers[i].full != reg.full {
name := get_register_name(RegisterId{idx=i})
msg := "%s register does not match - Expected %04x | Actual %04x"
fmt.eprintfln(msg, name, reg.full, CPU.registers[i].full)
fmt.println("\nFlags")
for state,flag in cpu.flags {
fmt.printf("%c:%d ",reflect.enum_string(flag)[0], state ? 1 : 0)
}
fmt.println()
failed_ref: bool
path,ok := strings.replace(os.args[1], ".bin", ".txt", 1)
ref_cpu,_ := parse_reference_cpu_state(path)
for reg_val,name in ref_cpu.registers {
if name == .ip && reg_val.full == 0 do continue
if cpu.registers[name].full != reg_val.full {
msg := "%s register does not match reference - Expected 0x%04x | Actual 0x%04x"
fmt.printfln(msg, name, reg_val.full, cpu.registers[name].full)
failed_ref = true
}
}
for f in Flag {
if ref_cpu.flags[f] != cpu.flags[f] {
msg := "%s flag does not match reference - Expected %d | Actual %d"
fmt.printfln(msg, f, ref_cpu.flags[f] ? 1 : 0, cpu.flags[f] ? 1 : 0)
failed_ref = true
}
}
if failed_ref {
os.exit(1)
}
}
if what_to_print == "instructions" || what_to_print == "all" {
print_instructions_stdout(instructions_list[:])
}
if len(os.args) > 3 && os.args[3] == "dump" {
f2,open_err := os.open("./listing-54.data", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
if open_err == nil {
bytes_written,write_err := os.write(f2, cpu.memory[:])
if write_err == nil {
fmt.println("Dumpped", bytes_written, "to disk: listing-54.data")
} else {
fmt.println("Error2:", write_err)
}
} else {
fmt.println("Error1:", open_err)
}
defer os.close(f)
}
}
+7 -6
View File
@@ -16,13 +16,14 @@ odin build . -out:sim8086
for asm_txt in asm_files/*.txt;
do
asm_listing=$(basename $asm_txt .txt)
./sim8086 asm_files/${asm_listing}.bin registers | awk '{print $1 $4}' | sort > temp1
cat $asm_txt | awk '/Final registers/,0' | tail -n +2 | awk '{print $1 $2}' | sort > temp2
diff -U0 --color temp1 temp2
output=$(./sim8086 asm_files/${asm_listing}.bin registers)
if [ $? -eq 1 ]; then
echo Listing $asm_listing Failed!
echo -e "Listing $asm_listing ${RED}Failed!${NC}"
echo "$output" | while IFS= read -r line; do
echo "$line"
done
echo -e ""
else
echo Listing $asm_listing Succeded!
echo -e "Listing $asm_listing: ${GREEN}Succeded!${NC}"
fi
done
rm temp1 temp2
+20 -3
View File
@@ -5,8 +5,14 @@ import "core:fmt"
import "core:math"
import "core:strings"
import "core:strconv"
import "core:reflect"
extract_expected_cpu_state :: proc(filename: string) -> (Cpu, bool) {
get_cpu_register_by_name :: proc(cpu: ^Cpu, name: string) -> ^RegisterValue {
reg,_ := reflect.enum_from_name(Register, name)
return &cpu.registers[reg]
}
parse_reference_cpu_state :: proc(filename: string) -> (Cpu, bool) {
cpu: Cpu
data,ok := os.read_entire_file(filename)
@@ -27,12 +33,23 @@ extract_expected_cpu_state :: proc(filename: string) -> (Cpu, bool) {
} else if c == ' ' {
space_count += 1
} else {
if line[i:i+5] != "flags" {
if line[i:i+5] == "flags" {
idx := i + 7
for idx < len(line) {
flag_name := fmt.tprintf("%cF", line[idx])
if flag,ok := reflect.enum_from_name(Flag, flag_name); ok {
cpu.flags[flag] = true
} else {
fmt.eprintfln("Error parsing flag enum %s", flag_name)
}
idx += 1
}
} else {
reg_name := line[i:i+2]
reg_value := get_cpu_register_by_name(&cpu, reg_name)
hex_string := line[i+6:i+10]
if hex_num,ok := strconv.parse_int(hex_string, 16); ok {
reg_value^.full = u16(hex_num)
reg_value^.full = i16(hex_num)
}
}
break
+32 -23
View File
@@ -13,18 +13,28 @@ Register :: enum {
cs,
ss,
ds,
ip,
}
RegisterValue :: struct #raw_union {
using _: struct {
low, high: byte,
},
full: u16,
full: i16,
}
Flags :: struct {
ZF: bool,
SF: bool,
Flag :: enum {
OF,
SF,
ZF,
AF,
PF,
CF,
// NOTE: These are the control flags, previous are status flags, justing noting in
// case we have to make that distinction in later modeling
TF,
DF,
IF,
}
WordSize :: enum {
@@ -36,13 +46,6 @@ WordSize :: enum {
}
None :: struct {}
Disp8 :: i8
Disp16 :: i16
Displacement :: union {
None,
Disp8,
Disp16
}
RegisterAccess :: enum {
Full,
@@ -51,21 +54,21 @@ RegisterAccess :: enum {
}
RegisterId :: struct {
idx: int,
name: Register,
access: RegisterAccess,
}
ImmediateSize :: enum {
Signed8,
Unsigned8,
Signed16,
_8,
_16,
}
Immediate :: struct {
value: u16,
value: i16,
size: ImmediateSize,
}
MemoryAddr :: struct {
addr_id: u8,
displacement: Displacement,
displacement: i16,
disp_size: ImmediateSize,
}
DirectAddress :: distinct i16
Jump :: distinct i8
@@ -92,7 +95,6 @@ OperandInfo :: enum {
SegmentRegister,
RegisterMemory,
Immediate,
ImmediateUnsigned,
Accumulator,
DirectAddress,
Jump,
@@ -118,6 +120,7 @@ InstructionInfo :: struct {
desc: string,
src: OperandInfo,
dst: OperandInfo,
flags: bit_set[Flag],
word_size: WordSize,
reg_info: RegisterEncodingBits,
has_flip: bool,
@@ -139,13 +142,19 @@ Instruction :: struct {
has_segment: Maybe(RegisterId),
has_lock: bool,
bytes_read: int,
segment_offset: int,
raw_data: []u8,
debug_msg: string,
}
Cpu :: struct {
flags: Flags,
registers: [12]RegisterValue,
memory: [dynamic]u8,
total_bytes_processed: int
DecodedInstructions :: struct {
inst_list: [dynamic]Instruction,
inst_map: map[int]Instruction,
total_bytes_decoded: int
}
Cpu :: struct {
flags: [Flag]bool,
registers: [Register]RegisterValue,
memory: [dynamic]u8,
}