添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

Repository files navigation

8086-emulator

Intel 8086仿真模拟器。

v4.0更新内容

  • GUI优化
  • 多种打印模式
  • DOS系统调用(获取系统时间、打印字符串等)
  • Features

  • 输入为8086汇编程序,支持几乎所有8086指令
  • 伪指令包含MASM5.0核心指令,采用Small存储模型
  • 支持中断、单步、暂停、断点、debug、交互式执行汇编
  • 支持王爽《汇编语言》前11章所有程序
  • 支持8086所有寄存器,包括高低字节
  • 支持段寄存器、栈、指令队列等特性
  • BIU和EU组成2级流水线
  • 图形界面GUI、汇编代码高亮和编辑
  • Usage

  • 本模拟系统有两种运行模式:CLI命令行模式和GUI图形界面模式,其中CLI支持输入和debug。
  • 运行输入为8086汇编代码,目前已经编写了近50条各种类型的汇编代码,在程序根目录 tests 文件夹中,也可以在网上查找汇编代码,稍加修改即可运行。
  • GUI运行说明

  • 支持代码编辑、运行、单步执行、暂停、停止等
  • 双击程序根目录下 mainUI.exe 即可运行,也可以执行以下代码运行:
  • $ python mainUI.py
  • New:创建空白汇编文件
  • Open:打开汇编汇编
  • Save:将当前编辑的汇编文件保存
  • 执行控制栏:
  • Load:加载当前程序到CPU,每次执行汇编前都需要Load
  • Run:顺序执行汇编代码,直到断点、中断或者结束
  • Pause:暂停执行,在Run的时候异步中断,再按Run或Step可以继续执行
  • Step:单步执行
  • Stop:终止执行,CPU停机
  • 寄存器栏:最左边的寄存器栏,显示当前寄存器状态
  • 段内存栏:
  • Stack Segment:显示栈空间内存,高亮SS:SP指针
  • Code Segment:显示代码段内存,高亮CS:IP指针
  • Data Segment:显示默认数据段内存
  • 编辑器:可以直接修改代码运行、保存等
  • Console:程序运行输出,暂不支持输入
  • 点击Load加载程序
  • 点击Run(连续)或者Step(单步)运行程序
  • 其他操作示例:

  • Run过程中可以点击Pause暂停,然后再Run或者Step
  • Open打开 tests 文件夹中的汇编文件,再次Load后运行
  • 修改汇编代码,Load后再运行
  • 随时按Stop使得CPU停机
  • CLI运行说明

    命令行交互CLI比GUI多的功能:

  • debug交互模式
  • 运行方法:在程序根目录命令行中执行:

    $ python main.py ./tests/Requirement/bubble_sort.asm -n

    第一个参数为需要执行的汇编程序(在 tests 文件夹) 后面有两个可选参数:

  • --nodebug :可简写为 -n ,关闭debug,持续运行直到断点或结束。示例:
    $ python main.py ./tests/Interrupt/show_date_time.asm -n
  • --interrupt :可简写为 -i ,显示中断信息。示例:
    $ python main.py ./tests/Interrupt/int3_test.asm -n -i

    Debug模式

  • 默认的debug模式,单步运行,每条指令执行结束打印cpu信息
  • 在单步暂停时,可以使用模拟DOS调试工具 DEBUG ,支持以下命令:
  • NMI (Non Maskable Interrupt) – 不可屏蔽,上升沿有效,最高优先级中断,引发int 2中断。
  • INTR (Interrupt Request) – 由I/O端口触发,可屏蔽,IF=1时响应,高电平有效,可以引发任意type中断.
  • 中断向量表 Interrupt Vector Table

  • 中断类型码8位,对应256个中断源。
  • 在8086PC机中,中断向量表指定放在内存地址0处。地址为 0000:0000 0000:03FF ,共1024个单元。
  • 一个表项占用两个字节,高地址放段地址,低地址放偏移地址
  • 为了简化,将处理程序从地址 1000:0000 开始放置,空间大小固定为 100h
  • DOS中断例程 INT 21H

  • MS-DOS API最初是86-DOS中的应用程序接口(API),并也被MS-DOS/PC-DOS及其他DOS兼容操作系统使用。大多数对DOS API的调用是使用中断21h(INT 21h)。在调用INT 21h时,在AH 寄存器中带有子函数号,其他寄存器中带有其他参数,从而调用各个DOS服务。DOS服务包括键盘输入、视频输入、磁盘文件访问、执行程序、内存分配及其他事务。
  • 已模拟实现的功能如下:
  • DATA SEGMENT PROMPT1 DB 'Current System Date is : $' DATE DB '0000-00-00$' ; date format year:month:day PROMPT2 DB 'Current System Time is : $' TIME DB '00:00:00$' ; time format hr:min:sec DATA ENDS CODE SEGMENT MAIN: MOV AX, DATA ; initialize DS MOV DS, AX ;-------------Print DATE------------------------- LEA BX, DATE ; BX=offset address of string DATE CALL GET_DATE ; call the procedure GET_DATE LEA DX, PROMPT1 ; DX=offset address of string PROMPT MOV AH, 09H ; print the string PROMPT INT 21H LEA DX, DATE ; DX=offset address of string TIME MOV AH, 09H ; print the string TIME INT 21H ;-------------Print TIME------------------------- LEA BX, TIME ; BX=offset address of string TIME CALL GET_TIME ; call the procedure GET_TIME LEA DX, PROMPT2 ; DX=offset address of string PROMPT MOV AH, 09H ; print the string PROMPT INT 21H LEA DX, TIME ; DX=offset address of string TIME MOV AH, 09H ; print the string TIME INT 21H MOV AH, 4CH ; return control to DOS INT 21H ;**************************************************************************; ;------------------------------ GET_TIME --------------------------------; ;**************************************************************************; GET_TIME: ; this procedure will get the current system time ; input : BX=offset address of the string TIME ; output : BX=current time PUSH AX ; PUSH AX onto the STACK PUSH CX ; PUSH CX onto the STACK MOV AH, 2CH ; get the current system time INT 21H MOV AL, CH ; set AL=CH , CH=hours CALL CONVERT ; call the procedure CONVERT MOV [BX], AX ; set [BX]=hr , [BX] is pointing to hr MOV AL, CL ; set AL=CL , CL=minutes CALL CONVERT ; call the procedure CONVERT MOV [BX+3], AX ; set [BX+3]=min , [BX] is pointing to min MOV AL, DH ; set AL=DH , DH=seconds CALL CONVERT ; call the procedure CONVERT MOV [BX+6], AX ; set [BX+6]=min , [BX] is pointing to sec POP CX ; POP a value from STACK into CX POP AX ; POP a value from STACK into AX RET ; return control to the calling procedure ;**************************************************************************; ;------------------------------ GET_DATE --------------------------------; ;**************************************************************************; GET_DATE: ; this procedure will get the current system time ; input : BX=offset address of the string TIME ; output : BX=current time PUSH AX ; PUSH AX onto the STACK PUSH CX ; PUSH CX onto the STACK MOV AH, 2AH ; get the current system Date INT 21H PUSH BX MOV BL,100 MOV AX,CX DIV BL MOV CX,AX POP BX MOV AL, CH ; set AL=CH , CH=Year CALL CONVERT ; call the procedure CONVERT MOV [BX], AX ; set [BX]=Year , [BX] is pointing to Year MOV AL, CL ; set AL=CL , CL=Year CALL CONVERT ; call the procedure CONVERT MOV [BX+2], AX ; set [BX+2]=Year , [BX] is pointing to Year MOV AL, DH ; set AL=DH , DH=Month CALL CONVERT ; call the procedure CONVERT MOV [BX+5], AX ; set [BX+5]=Month , [BX] is pointing to Year MOV AL, DL ; set AL=DH , DH=Day CALL CONVERT ; call the procedure CONVERT MOV [BX+8], AX ; set [BX+8]=Day , [BX] is pointing to Year POP CX ; POP a value from STACK into CX POP AX ; POP a value from STACK into AX RET ; return control to the calling procedure ;**************************************************************************; ;------------------------------- CONVERT --------------------------------; ;**************************************************************************; CONVERT: ; this procedure will convert the given binary code into ASCII code ; input : AL=binary code ; output : AX=ASCII code PUSH DX ; PUSH DX onto the STACK MOV AH, 0 ; set AH=0 MOV DL, 10 ; set DL=10 DIV DL ; set AX=AX/DL OR AX, 3030H ; convert the binary code in AX into ASCII POP DX ; POP a value from STACK into DX RET ; return control to the calling procedure CODE ENDS END MAIN

    运行结果如下:

    该示例多次调用DOS中断服务,获取系统日期和时间并打印,如需查看中断信息,可在命令行添加 -i 选项。

    BIOS中断例程 INT 10H

  • INT 10H是显示服务(Video Service)
  • 因基于BIOS测试不便,尚未实现
  • 用户中断例程

    用户可以自行编写中断例程,安装方法有两种:

  • CPU预加载,需简单更改 isr.py 文件
  • 运行汇编程序安装,使用MOSB指令(参考《汇编语言》第3版 12.8节代码)
  • 这里提供一个用户中断例程示例 isr7c.asm ,实现将字符串转换为大写, ds:si 指向字符串首地址:

    assume cs:code
    code segment
        upper: 
            push cx
            push si
        change:
            mov cl,[si]
            mov ch,0
            jcxz ok
            and byte ptr [si],11011111b
            inc si
            jmp short change
            pop si
            pop cx
    code ends
    end upper
    

    测试代码如下:

    assume cs:code,ds:data
    data segment
        msg db 'abcdefghijklmnopqrstuvwxyz$'
    data ends
    code segment
        start: mov ax,data
               mov ds,ax
               mov si,0           ;调用upper中断例程,转换为大写
               int 7ch
               mov dx,offset msg  ;lea dx,msg
               mov ah,9 
               int 21h            ; 调用BIOS中断例程,打印msg
               mov ax,4c00h
               int 21h
    code ends
    end start
    

    运行测试方式:

    $ python main.py ./tests/Interrupt/int7c_test.asm -n -i

    运行结果:

    可以看到本测试用例进行了3次中断调用:

  • 中断服务程序isr7c.asm将测试程序中的小写字母转换为大写
  • 中断服务程序dos_isr_21h的子程序09h打印该字符串
  • 中断服务程序dos_isr_21h的子程序4ch带返回值结束
  • 中断优先级和嵌套

  • 8086中软中断不能嵌套,硬中断可以嵌套。
  • 本模拟程序中凡调用中断例程均允许嵌套
  • x86构架的开端Intel 8086所有的内部寄存器、内部及外部数据总线都是16位宽,运算器、寄存器均为16位,是完全的16位微处理器,采用小端模式。20位外部地址总线,物理定址空间为1MB。

  • 使用最常见的Intel x86指令集。
  • 汇编语言大小写不敏感,为了统一风格,我们采用大写。
  • 操作数寻址方式 Addressing mode

    有效地址EA 段地址SA
  • 变长指令:单条指令的长度不固定,常用的指令设计成较短指令,不常用的指令设计成较长指令。
  • 指令编码非常紧凑:经常使用隐式操作数,使操作数不直接出现在二进制指令中。这样节省指令长度。
  • 丰富的操作数寻址方式
  • 8086CPU指令系统,它采用1~6个指令字节的变字长,包括操作码(第一字节)、寻址方式(第二字节)和操作数(第三到第六字节)三部分组成,指令格式如下:

    第一字节(BYTE 1)

  • 指令定义了处理器要执行的操作。操作码通常位于第一字节,某些指令的操作码会扩展到第二字节(即ModR/M字节)的REG域,故有时候REG域也被称为REG/Opcode域,用来指出该域的两种用途。
  • 绝大多数的指令的第一字节的高6个比特位(即BYTE1[7:2])是操作码,BYTE1[1]是D标志位,指明操作的方向,BYTE1[0]是W标志位,指示操作数的宽度。

    | Mod编码(二进制)| 释义 | | -------- | -------- | -------- | | 00 | 存储器模式,无位移量字节;如果R/M=110,则有一个16位的位移量 | | 01 | 存储器模式,8位位移量字节(1个字节) | | 10 | 存储器模式,16位位移量字节(2个字节)存储器模式,16位位移量字节(2个字节) | | 11 | 寄存器模式(无位移量) |

    REG域(BYTE2[5:3],即寄存器域)用来指示一个寄存器,可以是源操作数,也可以是目的操作数,由第一字节的D标志位指示。具体编码格式如下:

    R/M域(BYTE2[2:0],即寄存器/存储器域),用来指示另一个操作数,可以在存储器中,也可以在寄存器中。R/M域编码含义依赖于MOD域的设定。如果MOD=11(寄存器到寄存器模式),则R/M域标识第二个寄存器操作数。如果MOD是存储器模式(即00,01,10),则R/M指示如何如何计算存储器操作数的有效地址。

    8086指令集编码

    根据8086指令格式的形式,我们通过一个编码矩阵的形式来集中体现操作码(第一字节码)的对应编码。后续的第二~六字节因为涉及到不同的寻址方式、寄存器/存储器的选取以及立即数的值,这些都会导致每一条指令编码的不同,所以在设计指令格式时我们并未对后面字节进行详细编码,而是用抽象形式来体现后续字节。

    Opcode Map (Part 1)

    ADD
    Eb Gb ADD
    Ev Gv ADD
    Gb Eb ADD
    Gv Ev ADD
    AL Ib ADD
    AX Iv PUSH
    ES POP
    ES ADC
    Eb Gb ADC
    Ev Gv ADC
    Gb Eb ADC
    Gv Ev ADC
    AL Ib ADC
    AX Iv PUSH
    SS POP
    SS AND
    Eb Gb AND
    Ev Gv AND
    Gb Eb AND
    Gv Ev AND
    AL Ib AND
    AX Iv XOR
    Eb Gb XOR
    Ev Gv XOR
    Gb Eb XOR
    Gv Ev XOR
    AL Ib XOR
    AX Iv INC
    AX INC
    CX INC
    DX INC
    BX INC
    SP INC
    BP INC
    SI INC
    DI PUSH
    AX PUSH
    CX PUSH
    DX PUSH
    BX PUSH
    SP PUSH
    BP PUSH
    SI PUSH
    DI (1)表格的列代表Opcode Byte的前4位,即Hi;行代表Opcode Byte的后4位,即Lo。 (2)在单元中每一个指令名称的下方都标有该指令所对应的寄存器和相应字长,部分指令还标有寻址方式。具体的寄存器的分类介绍请详见文档寄存器部分

    Opcode Map (Opcode Extensions)

    GRP3a TEST
    Eb Ib GRP3b TEST
    Ev Iv Direct address. The instruction has no ModR/M byte; the address of the operand is encoded in the instruction. Applicable, e.g., to far JMP (opcode EA). A ModR/M byte follows the opcode and specifies the operand. The operand is either a general-purpose register or a memory address. If it is a memory address, the address is computed from a segment register and any of the following values: a base register, an index register, a displacement. The reg field of the ModR/M byte selects a general register. Immediate data. The operand value is encoded in subsequent bytes of the instruction. The instruction contains a relative offset to be added to the address of the subsequent instruction. Applicable, e.g., to short JMP (opcode EB), or LOOP. The ModR/M byte may refer only to memory. Applicable, e.g., to LES and LDS. The instruction has no ModR/M byte; the offset of the operand is encoded as a WORD in the instruction. Applicable, e.g., to certain MOVs (opcodes A0 through A3). The reg field of the ModR/M byte selects a segment register.

    DATA TRANSFER INSTRUCTIONS

    JMP与CALL译码过程:

    jmp word ptr [adr] -> jmp [adr], opbyte=2 -> ip = word(adr)
    jmp dword ptr [adr] -> jmp [adr], opbyte=4 -> ip = word(adr), cs = word(adr + 2)
    jmp cs:ip   -> cs = cs ip = ip
    jmp ip/reg  -> ip = word(ip/reg)
    call word ptr [adr] -> call [adr], opbyte=2 -> push ip, jmp [adr]
    call dword ptr [adr] -> call [adr], opbyte=4 -> push cs, push ip, jmp [adr]
    call cs:ip   -> push cs, push ip, jmp cs:ip
    call ip/reg  -> push ip, jmp ip/reg
    

    STRING MANIPULATION INSTRUCTIONS

    CMPSB cmpsb 将DS:[SI]处的字节8位表示的值减去ES:[DI]处的字节8位表示的值,结果影响标志寄存器PSW,若方向标志位DF=0,则DI与SI均加1,若方向标志位DF=1,则DI与SI均减1 CMPSW cmpsw 将DS:[SI]处的字16位表示的值减去ES:[DI]处的字16位表示的值,结果影响标志寄存器PSW,若方向标志位DF=0,则DI与SI均加2,若方向标志位DF=1,则DI与SI均减2 LODSB lodsb 将DS:[SI]处的字节8位拷贝至AL,若方向标志位DF=0,则SI加1,若方向标志位DF=1,则SI减1 LODSW lodsw 将DS:[SI]处的字16位拷贝至AX,若方向标志位DF=0,则SI加2,若方向标志位DF=1,则SI减2 STOSB stosb 将ES:[DI]处的字节8位拷贝至AL,若方向标志位DF=0,则DI加1,若方向标志位DF=1,则DI减1 STOSW stosw 将ES:[DI]处的字16位拷贝至AX,若方向标志位DF=0,则DI加2,若方向标志位DF=1,则DI减2 SCASB scasb 将AL表示的值减去ES:[DI]处的字节8位表示的值,结果影响标志寄存器PSW,若方向标志位DF=0,则DI加1,若方向标志位DF=1,则DI减1 SCASW scasw 将AX表示的值减去ES:[DI]处的字16位表示的值,结果影响标志寄存器PSW,若方向标志位DF=0,则DI加1,若方向标志位DF=1,则DI减1 REP MOVS/LODS/STOS rep movsw 若CX≠0,则重复一下操作:1、movsw;2、CX减1 REPE CMPS/SCAS repe cmpsw 若CX≠0,则重复一下操作:1、movsw;2、CX减1;3、若ZF不为1则退出循环 REPZ CMPS/SCAS repz cmpsw 若CX≠0,则重复一下操作:1、movsw;2、CX减1;3、若ZF不为1则退出循环 REPNE REPNE CMPS/SCAS repne cmpsw 若CX≠0,则重复一下操作:1、movsw;2、CX减1;3、若ZF不为0则退出循环 REPNZ REPNZ CMPS/SCAS repnz cmpsw 若CX≠0,则重复一下操作:1、movsw;2、CX减1;3、若ZF不为0则退出循环

    FLAG MANIPULATION INSTRUCTIONS

  • 每个汇编器都有一套不同的伪指令,我们采用了MASM非简化的伪指令。
  • 存储模型采用MASM 5.0支持的Small(小型)存储模型,所有代码在一个 64KB的段内,数据一般存储在其他64KB的段内(包括数据段、堆栈段和附加段)。
  • 标号、内存变量名、子程序名和宏名等都是标识符。 汇编时,我们将标号、变量、段名分开处理。

  • 数字标号(即变量):标识了变量的地址,为在代码中引用该变量提供了方便
  • 代码标号:冒号(:)结尾,通常用作跳转和循环指令的目标地址
  • 标号和变量的属性

  • 段属性:所在段的段地址
  • 偏移属性:段内偏移地址
  • 类型属性:
  • 标号:负数,近调用为-1,远调用为-2。
  • short短标号:标号在本段,距离在-128~+127之间
  • near近标号:标号在本段,距离在-32768~+32767之间
  • far远标号:当引用标号的指令和标号不在同一段
  • 变量:正数,其值为每个数据项的字节数。
  • DB定义的专变量的类型值为1
  • DW定义的变量的类型值为2
  • DD定义的变量的类型值为4
  • 与这3个属性相关的数值回送算符分别属是 SEG , OFFSET, TYPE MOV AX, SEG X ; 将变量X所在的段地址送入AX MOV BX, OFFSET Y ; 将变量Y的偏移地址送入BX MOV CX, TYPE Z ; 将变量Z的类型值送入CX
  • 具有语法检查机制,不符合标准的语法将报错(Compile Error)。

    define ten bytes 其后每个数据10字,压缩 BCD数据分配存储单元,可分配 10 个字节,但最多 只能输入18 个数字,数据后面不需要加 "H" DT 123456 duplicate 一般用来保留数据区, "DB 64 DUP(?)" 可为堆栈段保留 64 个字节 DB 3 DUP (0) 类型 PTR 变量 [ ± 常数表达式 ],类型为BYTE、WORD 、DWORD 、FWORD 、 QWORD 或 TBYTE MOV AX,WORD PTR [BX] LABEL 使变量具有不同的类型属性 VAL LABEL WORD 偶对齐伪指令。下面的内存变量从下一个偶地址单元开始分配 ALIGN ALIGN Imm 对齐伪指令。Imm为2的幂。 ALIGN 2 ORG EXP 调整偏移量伪指令 ORG 100H 代表程序结束 ASSUME 将段与段寄存器对应起来 ASSUM CS:CODESG SEGMENT ENDS 定义一个段 CODE SEGMENT
    ...
    CODE ENDS SHORT 指明标号在本段,距离在-128~+127之间 JMP SHORT S 指明标号在本段,距离在-32768~+32767之间 JMP NEAR PTR S 指明引用标号的指令和标号不在同一段 JMP FAR PTR S 返回变量或标号的段地址 MOV AL, SEG VAR OFFSET 返回变量或标号的偏移地址 MOV AL, OFFSET VAR 返回变量或标号的类型 ADD AX, TYPE S

    测试代码遵循 《汇编语言》王爽 一书中的格式

    下面使用一段简单的汇编语言源程序来说明。

    assume cs:codesg
    codesg segment
    	mov ax,0123H
    	mov bx,0456H
    	add ax,bx
    	add ax,ax
    	mov ax,4c00H
    	int 21H
    codesg ends
    

    1. 伪指令

    XXX segment
    XXX ends
    

    segment 和 ends 是一对成对使用的伪指令,这是在写可被编译器编译的汇编程序时,必须要用到的一对伪指令。segment 和 ends 的功能是定义一个段,segment 说明一个段开始,ends 说明一个段结束。一个段必须有一个名称来标识,使用格式为:

    段名 segment
    段名 ends
    

    (2) end

    end 是一个汇编程序的结束标记,编译器在编译汇编程序的过程中,如果碰到了伪指令 end,就结束对源程序的编译。

    (3) assume

    这条伪指令的含义为“假设“。它假设某一段寄存器和程序中的某一个用 segment...ends 定义的段相关联。通过 assume 说明这种关联,在需要的情况下,编译程序可以将段寄存器和某一个具体的段相联系。

    如上述程序中定义了一个名为 codesg 的段,在这个段中存放代码,所以这个段是一个代码段。在程序的开头,用 assume cs:codesg 将用作代码段的段 codesg 和 CPU 中的段寄存器 cs 联系起来

    (4) db、dw、dd

    当我们希望像 C 语言数组使用连续内存存储较多数据时,可以使用 db、dw、dd 指令。

    使用方法为

    db 0123h,0456h,0789h,0abch,0defh,0fedh,0cbah,0987h
    

    其中 DB 定义字节型数据,DW 定义字型数据,DD 定义双字型数据。

    (5) dup

    dup 和 db、dw、dd 等数据定义伪指令配合使用,用来进行数据的重复。如:

    db 3 dup (0,1,2)
    

    定义了 9 个字节,他们是 0、1、2、0、1、2、0、1、2,相当于 db 0,1,2,0,1,2,0,1,2。

    2. 源程序中的”程序“

    用汇编语言写的源程序,包括伪指令和汇编指令。源程序中的汇编指令组成了最终由计算机执行的程序,而伪指令是由编译器来处理的,它们并不实现我们编程的最终目的。这里所说的程序就是指源程序中最终由计算机执行、处理的指令或数据。

    3. 标号

    汇编源程序中,除了汇编指令和伪指令外,还有一些标号,如"codesg"。一个标号指代了一个地址。比如 codesg 在 segment 的前面,作为一个段的名称,这个段的名称最终将被编译、连接程序处理成为一个段的段地址。

    4. 程序的结构

    源程序是由一些段构成的。我们可以在这些段中存放代码、数据、或将某个段当作栈空间。

    5. 程序返回

    一个程序结束后,应该将 CPU 的控制权交还给使它得以运行的程序,我们称这个过程为: 程序返回。要实现程序返回,应该在程序的末尾添加返回的程序段。如上述源程序中

    mov ax,4c00H
    int 21H
    

    它们实现了安全退出程序的功能。

    6. 语法错误和逻辑错误

    一般来说,程序在编译时被编译器发现的错误是语法错误。

    在源程序编译后,在运行时发生的错误是逻辑错误。

    本次实验的测试用例采用8086汇编语言编写,伪指令包含MASM5.0核心指令,采用Small存储模型。我们首先设计出斐波那契数列汇编程序和冒泡排序汇编程序两个基本测试用例,并在此基础上设计了数据传送类、测试指令类、算术类、字符串类、综合类、查找类、基本要求测试用例类等汇编程序。

    测试程序集

  • 数据传送类(Data_trandfer)
  • Cache Memory

  • 值得注意的是,8086不支持 L1 或者 L2 cache memory。但为了更真实地模拟cpu运行,我们将cpu中的一级缓存、二级缓存等抽象为一个cpu类下的指令缓存器:cache memory。
  • 我们假设只有一条cache line(大小为64KB),让cache memory存入已经载入内存中的程序段。
  • 流水线和时序产生器(Clock)

    8086流水线模拟

  • 8086系统的时钟频率为4.77MHz~10MHz,每个时钟周期约为200ns。我们通过sleep函数降低时钟频率,每个周期均sleep一定时间以观察cpu运行细节。
  • 8086处理器的流水线超级简单,只有取指和执行两级。BIU(Bus Interface Unit)单元负责取指,EU(Execution Unit)单元负责指令译码。故而划分取指周期T1和执行周期T2:
  • 取值周期(控制指令流)
  • 执行周期(控制数据流):译码
  • 取指单元BIU(Bus Interface Unit)

  • It generates the 20 bit physical address for memory access.
  • It fetches instructions from the memory.
  • It transfers data to and from the memory and I/O.
  • Maintains the 6 byte prefetch instruction queue(supports pipelining).
  • Instruction Queue

  • Pre-fetches up to 6 instructions(8086最长指令为6字节) in advance。
  • 我们这里按照假设每次pre-fetch 6条指令。
  • 当EU执行转移类指令时,指令队列立即清空,BIU又重新开始从内存中取转移目标处的指令代码送往指令队列。
  • BIU fills in the queue until the entire queue is full.(6 byte FIFO)
  • BIU restarts filling in the queue when at least two locations of queue are vacant.
  • Pipelining:Fetching the next instruction (by BIU from CS) while executing the current instruction 。
  • Gets flushed whenever a branch instruction occurs.
  • Segment Registers

    Rules of Segmentation Segmentation process follows some rules as follows:

  • The starting address of a segment should be such that it can be evenly divided by 16.
  • Minimum size of a segment can be 16 bytes and the maximum can be 64 kB.
  • 我们假设4个段长度均为最大长度64kB(10000H),4个段默认分布如上图。存储器对应关系如下:
  • Instruction Pointer (IP)

  • It is a 16 bit register. It holds offset of the next instructions in the Code Segment.
  • IP is incremented after every instruction byte is fetched.
  • IP gets a new value whenever a branch instruction occurs.
  • CS is multiplied by 10H to give the 20 bit physical address of the Code Segment.
  • Address of the next instruction is calculated as CS x 10H + IP.
  • Address Generation Circuit

  • The BIU has a Physical Address Generation Circuit.
  • It generates the 20 bit physical address using Segment and Offset addresses using the formula:
  • Physical Address = Segment Address x 10H + Offset Address

    执行单元EU(Execution Unit)

    增加了一个指令寄存器IR用于存放当前指令。

    算术逻辑单元Arithmetic Logic Unit(ALU)

    Performs 8 and 16 bit arithmetic and logic operations

    指令译码器 Instruction Decoder

    The instruction decoder decodes instruction in IR and sends the information to the control circuit for execution.

    控制电路Control Circuit

    对指令进行分类,调用对应模块执行。

    General purpose registers

  • AX、BX、CD、DX
  • Special purpose registers

  • SP、BP、SI、D
  • Instruction Register

    The EU fetches an opcode from the queue into the instruction register.

    Flag/Status Register

  • 6 Status flags:
  • carry flag(CF)
  • parity flag(PF)
  • auxiliary carry flag(AF)
  • zero flag(Z)
  • sign flag(S)
  • overflow flag (O)
  • 3 Control flags:
  • trap flag(TF)
  • interrupt flag(IF)
  • direction flag(DF)
  • Future Works

  • 流水线优化
  • 《Intel Software Developer’s Manual》
  • 《深入理解计算机系统》
  • 《汇编语言》
  • 《编译原理》
  • 《x86汇编语言 从实模式到保护模式完整版》
  • https://codegolf.stackexchange.com/questions/4732/emulate-an-intel-8086-cpu
  • http://www.c-jump.com/CIS77/CPU/x86/index.html
  • https://fms.komkon.org/EMUL8/HOWTO.html
  • https://www.swansontec.com/sintel.html
  • http://ref.x86asm.net/coder32.html
  • https://wiki.osdev.org/X86-64_Instruction_Encoding
  •