Compare commits

..
15 Commits
30 changed files with 55170 additions and 154 deletions

No files matched your search

+1
View File
@@ -5,3 +5,4 @@
/decoder8086 /decoder8086
/performance-aware /performance-aware
/sim8086 /sim8086
/listing-*.data
+94 -6
View File
@@ -1,8 +1,96 @@
#+OPTIONS: toc:nil #+OPTIONS: toc:nil
* 8086 CPU Emulator * 8086 CPU Simulator
Following along with [[https://www.computerenhance.com/p/table-of-contents][Performance Aware by Computer Enhance]]
* License 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.
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 ** Features
a paid course so maybe pay for the course if you want to use these files?
- *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.
+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
+29 -17
View File
@@ -66,7 +66,9 @@ get_op :: proc(inst: Instruction) -> (Op, bool) {
} }
return op, interseg 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{} operand: Operand = None{}
switch opinfo { switch opinfo {
case .None: case .None:
@@ -97,10 +99,13 @@ parse_operand :: proc(inst: InstructionInfo, opinfo: OperandInfo, data: []u8, pr
op = (DirectAddress)(get_i16(data[2:])) op = (DirectAddress)(get_i16(data[2:]))
processed^ += 2 processed^ += 2
} else { } else {
op = MemoryAddr{ addr_id = rm , displacement = None{} } op = MemoryAddr{ addr_id = rm }
} }
} else if mod == 1 { } 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 processed^ += 1
} else if mod == 2 { } else if mod == 2 {
op = MemoryAddr{ addr_id = rm , displacement = get_i16(data[2:]) } op = MemoryAddr{ addr_id = rm , displacement = get_i16(data[2:]) }
@@ -119,14 +124,11 @@ parse_operand :: proc(inst: InstructionInfo, opinfo: OperandInfo, data: []u8, pr
if inst.has_sign_extension { if inst.has_sign_extension {
word_signed &&= data[0] & 0b0000_0010 == 0 word_signed &&= data[0] & 0b0000_0010 == 0
} }
value: u16 = word_signed ? u16(get_i16(data[data_idx:])) : u16(data[data_idx]) value: i16 = word_signed ? get_i16(data[data_idx:]) : i16(i8(data[data_idx]))
operand = Immediate { value = value, size = word_signed ? .Signed16 : .Signed8 } operand = Immediate { value = value, size = word_signed ? ._16 : ._8 }
processed^ += word_signed ? 2 : 1 processed^ += word_signed ? 2 : 1
case .ImmediateUnsigned:
operand = Immediate { value = u16(data[processed^]), size = .Unsigned8 }
processed^ += 1
case .Accumulator: case .Accumulator:
operand = RegisterId { name = Register(0), access = word ? .Full : .Low } operand = RegisterId { name = .ax, access = word ? .Full : .Low }
case .DirectAddress: case .DirectAddress:
// operand = DirectAddress { value = get_i16(data[1:]) } // operand = DirectAddress { value = get_i16(data[1:]) }
operand = (DirectAddress)(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 // 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) operand = (Jump)((i8)(data[1]) + 2)
case .VariablePort: case .VariablePort:
operand = RegisterId { name = 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: case .ShiftRotate:
v_flag := data[0] & 0b10 != 0 v_flag := data[0] & 0b10 != 0
operand = v_flag ? RegisterId { name = Register(1), access = .Low } : Immediate { value = 1 } operand = v_flag ? RegisterId { name = .cx, access = .Low } : Immediate { value = 1 }
case .Repeat: case .Repeat:
bits := (data[1] & 0b1110) >> 1 bits := (data[1] & 0b1110) >> 1
w := (data[1] & 0b1) == 1 ? "w" : "b" 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)) operand = Repeat(fmt.aprintf("%s%s", rep, w))
processed^ += 1 processed^ += 1
case .DirectWithinSegment: case .DirectWithinSegment:
value := (int)(get_i16(data[1:])) + CPU.total_bytes_processed + 3 value := (int)(get_i16(data[1:])) + total_bytes_processed + 3
operand = Immediate { value = u16(value), size = .Signed16 } operand = Immediate { value = i16(value), size = ._16 }
processed^ += 2 processed^ += 2
case .Intersegment: case .Intersegment:
operand = Intersegment { operand = Intersegment {
@@ -167,10 +176,11 @@ parse_operand :: proc(inst: InstructionInfo, opinfo: OperandInfo, data: []u8, pr
return operand 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 idx := 0
has_segment: Maybe(RegisterId) has_segment: Maybe(RegisterId)
has_lock: bool has_lock: bool
total_bytes_processed: int
for idx < bytes_to_read { for idx < bytes_to_read {
instruction: Instruction instruction: Instruction
processed := 1 processed := 1
@@ -234,8 +244,8 @@ decode_data :: proc(inst_list: ^[dynamic]Instruction, data: []u8, bytes_to_read:
case .Always16: word = true case .Always16: word = true
} }
dst_opr = parse_operand(inst, inst.dst, 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, word, has_segment) src_opr = parse_operand(inst, inst.src, data[idx:], &processed, total_bytes_processed, word, has_segment)
if flip { if flip {
src_opr, dst_opr = dst_opr, src_opr 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.info = inst
instruction.has_lock = has_lock instruction.has_lock = has_lock
instruction.has_segment = has_segment instruction.has_segment = has_segment
instruction.segment_offset += total_bytes_processed
instruction.opname = inst.opname instruction.opname = inst.opname
instruction.opname,instruction.indirect_intersegment = get_op(instruction) 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_lock = false
has_segment = nil has_segment = nil
CPU.total_bytes_processed = idx total_bytes_processed = idx
} }
return total_bytes_processed
} }
+133 -42
View File
@@ -5,70 +5,161 @@ import "core:fmt"
import "core:math" import "core:math"
import "core:strings" import "core:strings"
import "core:reflect" 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 { #partial switch opr in operand {
case Immediate: case Immediate:
return opr.value return i16(opr.value)
case RegisterId: case RegisterId:
reg_val := CPU.registers[opr.name] reg_val := cpu.registers[opr.name]
switch opr.access { switch opr.access {
case .Low, .High: 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: 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 return 0
} }
set_register_value :: proc(reg: RegisterId, value: u16) { check_flags :: proc(cpu: ^Cpu, flags: bit_set[Flag], dst: i16, src: i16, result: i16, is_add: bool) {
switch reg.access { if .ZF in flags do cpu.flags[.ZF] = result == 0
case .Low: if .SF in flags do cpu.flags[.SF] = (result >> 15) & 0x1 == 1
CPU.registers[reg.name].low = u8(value) if .CF in flags {
case .High: cpu.flags[.CF] = is_add ? u32(dst) + u32(src) > 0xFFFF : src > dst
CPU.registers[reg.name].high = u8(value) }
case .Full: if .AF in flags {
CPU.registers[reg.name].full = u16(value) 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
} }
} }
get_cpu_register_by_name :: proc(cpu: ^Cpu, name: string) -> ^RegisterValue { perform_store :: proc(cpu: ^Cpu, value: i16, operand: Operand, is_word: bool) {
reg,_ := reflect.enum_from_name(Register, name) #partial switch opr in operand {
return &cpu.registers[reg] case RegisterId:
switch opr.access {
case .Low:
cpu.registers[opr.name].low = u8(value)
case .High:
cpu.registers[opr.name].high = u8(value)
case .Full:
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)
}
} }
check_zero_flag :: proc(value: u16) { execute_instruction :: proc(cpu: ^Cpu, inst: Instruction) {
CPU.flags[.ZF] = value == 0 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_sign_flag :: proc(value: u16) { case .LOOP:
CPU.flags[.SF] = value >> 15 == 1 cpu.registers[.cx].full -= 1
} jump = cpu.registers[.cx].full != 0
case .LOOPNZ, .LOOPNE:
execute_instruction :: proc(inst: Instruction) { cpu.registers[.cx].full -= 1
if reg,ok := inst.dst.(RegisterId); ok { // 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]
}
if jump {
cpu.registers[.ip].full += i16(jmp_offset)
cpu.registers[.ip].full -= i16(inst.bytes_read)
}
return
}
status_flags := bit_set[Flag]{.OF, .SF, .ZF, .AF, .PF, .CF}
#partial switch inst.opname { #partial switch inst.opname {
case .MOV: case .MOV:
src_val := get_operand_value(inst.src) src_val := perform_load(cpu, inst.src, inst.is_word)
set_register_value(reg, src_val) perform_store(cpu, src_val, inst.dst, inst.is_word)
case .ADD: case .ADD:
src_val := get_operand_value(inst.src) src_val := perform_load(cpu, inst.src, inst.is_word)
val := CPU.registers[reg.name].full + src_val dst_val := perform_load(cpu, inst.dst, inst.is_word)
set_register_value(reg, val) val := dst_val + src_val
check_zero_flag(val) perform_store(cpu, val, inst.dst, inst.is_word)
check_sign_flag(val) check_flags(cpu, status_flags, dst_val, src_val, val, true)
case .SUB: case .SUB:
src_val := get_operand_value(inst.src) src_val := perform_load(cpu, inst.src, inst.is_word)
val := CPU.registers[reg.name].full - src_val dst_val := perform_load(cpu, inst.dst, inst.is_word)
set_register_value(reg, val) val := dst_val - src_val
check_zero_flag(val) perform_store(cpu, val, inst.dst, inst.is_word)
check_sign_flag(val) check_flags(cpu, status_flags, dst_val, src_val, val, false)
case .CMP: case .CMP:
src_val := get_operand_value(inst.src) src_val := perform_load(cpu, inst.src, inst.is_word)
val := CPU.registers[reg.name].full - src_val dst_val := perform_load(cpu, inst.dst, inst.is_word)
check_zero_flag(val) val := dst_val - src_val
check_sign_flag(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, CALL,
JMP, JMP,
JNZ, JNZ,
JNE,
JNGE, JNGE,
JE, JE,
JZ, JZ,
@@ -90,7 +91,6 @@ Op :: enum {
JBE, JBE,
JO, JO,
JS, JS,
JNE,
JNL, JNL,
JGE, JGE,
JNLE, JNLE,
@@ -106,6 +106,7 @@ Op :: enum {
LOOP, LOOP,
LOOPZ, LOOPZ,
LOOPNZ, LOOPNZ,
LOOPNE,
JCXZ, JCXZ,
} }
@@ -193,16 +194,13 @@ instructions := [?]InstructionInfo {
dst = .Accumulator, src = .Register, dst = .Accumulator, src = .Register,
reg_info = .FirstByteLast3, has_flip = true, word_size = .Always16 }, reg_info = .FirstByteLast3, has_flip = true, word_size = .Always16 },
{ opname = .IN, desc = "", mask = 0b11111110, encoding = 0b11100100, { 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, { opname = .IN, desc = "", mask = 0b11111110, encoding = 0b11101100,
dst = .Accumulator, src = .VariablePort, dst = .Accumulator, src = .VariablePort, word_size = .LastBit, },
word_size = .LastBit, },
{ opname = .OUT, desc = "", mask = 0b11111110, encoding = 0b11100110, { opname = .OUT, desc = "", mask = 0b11111110, encoding = 0b11100110,
dst = .ImmediateUnsigned, src = .Accumulator, dst = .VariablePort, src = .Accumulator, word_size = .LastBit },
word_size = .LastBit, },
{ opname = .OUT, desc = "", mask = 0b11111110, encoding = 0b11101110, { opname = .OUT, desc = "", mask = 0b11111110, encoding = 0b11101110,
dst = .VariablePort, src = .Accumulator, dst = .VariablePort, src = .Accumulator, word_size = .LastBit, },
word_size = .LastBit, },
{ opname = .TEST, desc = "", mask = 0b11111110, encoding = 0b10101000, { opname = .TEST, desc = "", mask = 0b11111110, encoding = 0b10101000,
dst = .Accumulator, src = .Immediate, word_size = .LastBit }, dst = .Accumulator, src = .Immediate, word_size = .LastBit },
{ opname = .XLAT, desc = "", mask = 0b11111111, encoding = 0b11010111,}, { opname = .XLAT, desc = "", mask = 0b11111111, encoding = 0b11010111,},
+2 -38
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 { get_memory_string :: proc(memoryAddr: MemoryAddr, has_segment: Maybe(RegisterId)) -> string {
disp: string disp: string
switch value in memoryAddr.displacement { value := memoryAddr.displacement
case None:
disp = ""
case Disp8:
if value != 0 { if value != 0 {
disp = fmt.aprintf(" %s %d", value > 0 ? "+" : "-", math.abs(value)) 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 seg_string: string
if segreg, ok := has_segment.?; ok { if segreg, ok := has_segment.?; ok {
seg_string = fmt.aprintf("%s:", get_register_name(segreg)) seg_string = fmt.aprintf("%s:", get_register_name(segreg))
@@ -59,21 +51,6 @@ get_memory_string :: proc(memoryAddr: MemoryAddr, has_segment: Maybe(RegisterId)
return text 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 { get_register_name :: proc(reg_id: RegisterId) -> string {
low_names := [?]string{"al", "cl", "dl", "bl"} low_names := [?]string{"al", "cl", "dl", "bl"}
high_names := [?]string{"ah", "ch", "dh", "bh"} high_names := [?]string{"ah", "ch", "dh", "bh"}
@@ -93,14 +70,7 @@ get_operand_string :: proc(operand: Operand, has_segment: Maybe(RegisterId)) ->
case RegisterId: case RegisterId:
string_val = get_register_name(val) string_val = get_register_name(val)
case Immediate: case Immediate:
switch val.size { string_val = fmt.aprintf("%d", val.value)
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))
}
case MemoryAddr: case MemoryAddr:
string_val = get_memory_string(val, has_segment) string_val = get_memory_string(val, has_segment)
case DirectAddress: case DirectAddress:
@@ -154,15 +124,9 @@ get_instruction_string :: proc(inst_info: InstructionInfo, instruction: Instruct
interseg_string = " far" interseg_string = " far"
} }
fmt.sbprintf(&instruction_builder, "%s%s %s%s", opname, interseg_string, size_string, src_str) 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 { } else {
fmt.sbprintf(&instruction_builder, "%s %s%s, %s", opname, size_string, dst_str, src_str) fmt.sbprintf(&instruction_builder, "%s %s%s, %s", opname, size_string, dst_str, src_str)
} }
}
// Prepare padding and comment to add debug info // Prepare padding and comment to add debug info
b_len := strings.builder_len(instruction_builder) b_len := strings.builder_len(instruction_builder)
+55 -20
View File
@@ -6,10 +6,7 @@ import "core:fmt"
import "core:math" import "core:math"
import "core:strings" import "core:strings"
import "core:reflect" import "core:reflect"
import "core:strconv"
CPU := Cpu {
memory = make([dynamic]u8, 65536),
}
main :: proc() { main :: proc() {
f,err := os.open(os.args[1]) f,err := os.open(os.args[1])
@@ -36,42 +33,80 @@ main :: proc() {
instruction_list := make([dynamic]string, 0, 512) instruction_list := make([dynamic]string, 0, 512)
instructions_list := make([dynamic]Instruction, 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 { 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" { if what_to_print == "registers" || what_to_print == "all" {
fmt.println("\nRegisters") fmt.println("Registers")
for reg_val,name in CPU.registers { for reg_val,name in cpu.registers {
full := fmt.aprintf("%s: %d ", name, reg_val.full) full := fmt.aprintf("%s: %d ", name, i16(reg_val.full))
hex := fmt.aprintf("0x%04x ", reg_val.full) hex := fmt.aprintf("0x%04x ", u16(reg_val.full))
fmt.printf("%s %*[1]s %s %*[4]s %08b %08b", fmt.printf("%s %*[1]s %s %*[4]s %08b %08b",
full, 18 - len(full), "|", hex, 10 - len(hex), "|", reg_val.high, reg_val.low) full, 18 - len(full), "|", hex, 10 - len(hex), "|", reg_val.high, reg_val.low)
fmt.println() fmt.println()
} }
fmt.println("\nFlags") fmt.println("\nFlags")
for state,flag in CPU.flags { for state,flag in cpu.flags {
fmt.printfln("%c: %d",reflect.enum_string(flag)[0], state ? 1 : 0) 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) path,ok := strings.replace(os.args[1], ".bin", ".txt", 1)
ref_cpu,_ := extract_reference_cpu_state(path) ref_cpu,_ := parse_reference_cpu_state(path)
for reg_val,name in ref_cpu.registers { for reg_val,name in ref_cpu.registers {
if CPU.registers[name].full != reg_val.full { if name == .ip && reg_val.full == 0 do continue
msg := "%s register does not match reference - Expected %04x | Actual %04x" if cpu.registers[name].full != reg_val.full {
fmt.eprintfln(msg, name, reg_val.full, CPU.registers[name].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 { for f in Flag {
if ref_cpu.flags[f] != CPU.flags[f] { if ref_cpu.flags[f] != cpu.flags[f] {
msg := "%s flag does not match reference - Expected %t | Actual %t" msg := "%s flag does not match reference - Expected %d | Actual %d"
fmt.eprintfln(msg, f, ref_cpu.flags[f], CPU.flags[f]) 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" { if what_to_print == "instructions" || what_to_print == "all" {
print_instructions_stdout(instructions_list[:]) 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; for asm_txt in asm_files/*.txt;
do do
asm_listing=$(basename $asm_txt .txt) asm_listing=$(basename $asm_txt .txt)
./sim8086 asm_files/${asm_listing}.bin registers | awk '{print $1 $4}' | sort > temp1 output=$(./sim8086 asm_files/${asm_listing}.bin registers)
cat $asm_txt | awk '/Final registers/,0' | tail -n +2 | awk '{print $1 $2}' | sort > temp2
diff -U0 --color temp1 temp2
if [ $? -eq 1 ]; then 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 else
echo Listing $asm_listing Succeded! echo -e "Listing $asm_listing: ${GREEN}Succeded!${NC}"
fi fi
done done
rm temp1 temp2
+7 -2
View File
@@ -7,7 +7,12 @@ import "core:strings"
import "core:strconv" import "core:strconv"
import "core:reflect" import "core:reflect"
extract_reference_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 cpu: Cpu
data,ok := os.read_entire_file(filename) data,ok := os.read_entire_file(filename)
@@ -44,7 +49,7 @@ extract_reference_cpu_state :: proc(filename: string) -> (Cpu, bool) {
reg_value := get_cpu_register_by_name(&cpu, reg_name) reg_value := get_cpu_register_by_name(&cpu, reg_name)
hex_string := line[i+6:i+10] hex_string := line[i+6:i+10]
if hex_num,ok := strconv.parse_int(hex_string, 16); ok { if hex_num,ok := strconv.parse_int(hex_string, 16); ok {
reg_value^.full = u16(hex_num) reg_value^.full = i16(hex_num)
} }
} }
break break
+15 -15
View File
@@ -13,13 +13,14 @@ Register :: enum {
cs, cs,
ss, ss,
ds, ds,
ip,
} }
RegisterValue :: struct #raw_union { RegisterValue :: struct #raw_union {
using _: struct { using _: struct {
low, high: byte, low, high: byte,
}, },
full: u16, full: i16,
} }
Flag :: enum { Flag :: enum {
@@ -45,13 +46,6 @@ WordSize :: enum {
} }
None :: struct {} None :: struct {}
Disp8 :: i8
Disp16 :: i16
Displacement :: union {
None,
Disp8,
Disp16
}
RegisterAccess :: enum { RegisterAccess :: enum {
Full, Full,
@@ -64,17 +58,17 @@ RegisterId :: struct {
access: RegisterAccess, access: RegisterAccess,
} }
ImmediateSize :: enum { ImmediateSize :: enum {
Signed8, _8,
Unsigned8, _16,
Signed16,
} }
Immediate :: struct { Immediate :: struct {
value: u16, value: i16,
size: ImmediateSize, size: ImmediateSize,
} }
MemoryAddr :: struct { MemoryAddr :: struct {
addr_id: u8, addr_id: u8,
displacement: Displacement, displacement: i16,
disp_size: ImmediateSize,
} }
DirectAddress :: distinct i16 DirectAddress :: distinct i16
Jump :: distinct i8 Jump :: distinct i8
@@ -101,7 +95,6 @@ OperandInfo :: enum {
SegmentRegister, SegmentRegister,
RegisterMemory, RegisterMemory,
Immediate, Immediate,
ImmediateUnsigned,
Accumulator, Accumulator,
DirectAddress, DirectAddress,
Jump, Jump,
@@ -127,6 +120,7 @@ InstructionInfo :: struct {
desc: string, desc: string,
src: OperandInfo, src: OperandInfo,
dst: OperandInfo, dst: OperandInfo,
flags: bit_set[Flag],
word_size: WordSize, word_size: WordSize,
reg_info: RegisterEncodingBits, reg_info: RegisterEncodingBits,
has_flip: bool, has_flip: bool,
@@ -148,13 +142,19 @@ Instruction :: struct {
has_segment: Maybe(RegisterId), has_segment: Maybe(RegisterId),
has_lock: bool, has_lock: bool,
bytes_read: int, bytes_read: int,
segment_offset: int,
raw_data: []u8, raw_data: []u8,
debug_msg: string, debug_msg: string,
} }
DecodedInstructions :: struct {
inst_list: [dynamic]Instruction,
inst_map: map[int]Instruction,
total_bytes_decoded: int
}
Cpu :: struct { Cpu :: struct {
flags: [Flag]bool, flags: [Flag]bool,
registers: [Register]RegisterValue, registers: [Register]RegisterValue,
memory: [dynamic]u8, memory: [dynamic]u8,
total_bytes_processed: int
} }