简单的汇编模拟器教程(JavaScript)部分2[译]

在部分1我们写了CPU部分。在第二部分也是最后一部分,我们会写内存,控制台输出和用户界面。

内存

我们使用一个简单的数组来表示内存。每个地址存储一个JavaScript数值。理论上,内存可以储存超过一个字节大小的值,但我们的CPU代码确保所有值都是0~255.

内存有3个函数。Load从指定地址取出一个字节,Store把一个字节写入指定地址。两个函数都会在指定地址超过有效地址空间时抛出错误。第三个函数把所有内存值设为0.我们用它来初始化或是重置模拟器。

var memory = Array(256);

function load(address) {
    if (address < 0 || address >= memory.length) {
        throw "Memory access violation. Address: " + address;
    }
    return memory[address];
};

function store(address, value) {
    if (address < 0 || address >= memory.length) {
        throw "Memory access violation. Address: " + address;
    }
    memory[address] = value;
};

function reset() {
    for (var i=0; i < memory.length; i++) {
        memory[i] = 0;
    }
};

完整代码在memory.js

控制台输出

最后一部分是控制台输出。它能显示24个字符,把内存的最后24个字符映射到输出。所以程序需要什么输出,把数据写到内存的最后24字节就行了。

汇编器

在我们完成我们的虚拟机的最后一部分后,我们还少了一块。我们如何把代码转化成CPU指令?

我们需要写自己的汇编器。汇编器会遍历每一行代码,解析操作符然后生成CPU指令。

为了解析代码,我们使用正则表达式。这只是简化的途径,因为不可能用一个正则型解析所有代码,我们会需要生成一个抽象语法树。这里我们只使用正则型。

正则型定义如下

// Matches: "label: INSTRUCTION (["')OPERAND1(]"'), (["')OPERAND2(]"')
// GROUPS:      1       2               3                    7
var regex = /^[\t ]*(?:([.A-Za-z]\w*)[:])?(?:[\t ]*([A-Za-z]{2,4})(?:[\t ]+(\[(\w+((\+|-)\d+)?)\]|\".+?\"|\'.+?\'|[.A-Za-z0-9]\w*)(?:[\t ]*[,][\t ]*(\[(\w+((\+|-)\d+)?)\]|\".+?\"|\'.+?\'|[.A-Za-z0-9]\w*))?)?)?/;

// Regex group indexes
var GROUP_LABEL = 1;  
var GROUP_OPCODE = 2;  
var GROUP_OPERAND1 = 3;  
var GROUP_OPERAND2 = 7;  

汇编器有一个主函数名为run。它接受代码作为参数。解析代码然后生成指令。返回值是一个指令数组,然后可以装入内存。这个返回的数组就是我们随后可以在模拟器中运行的程序。

在汇编语言中,每一条指令需要写在独立的行上。这样便于我们解析。运行汇编器可以:

  1. 逐行分割代码。每一行独立处理
  2. 确保每行有一条合法指令
  3. 根据指令读取操作数。每条指令有0-2个操作数。函数readOperand会解析这个值来查看操作数是寄存器还是内存地址还是常数。返回数据来源(寄存器,寄存器地址,内存地址,常数)和值
  4. 根据指令和操作数类型确定正确的操作码。正如第一部分所写,每个操作数对应一条指令。
  5. 增加最后一条指令包括操作数到代码数组。
  6. 回到步骤1,知道所有代码行都被解析,返回代码数组。
function run(code) {  
    var code = [];
    var opCode;
    var lines = code.split('\n');

    for (var i = 0, l = lines.length; i < l; i++) {
        var match = regex.exec(lines[i]);

        if (match[GROUP_OPCODE]) {
            var instr = match[GROUP_OPCODE].toUpperCase();
            switch (instr) {
                case 'ADD':
                    var op1 = readOperand(match[GROUP_OPERAND1]);
                    var op2 = readOperand(match[GROUP_OPERAND2]);

                    if (op1.type === "register" && op2.type === "register")
                        opCode = OpCodes.ADD_REG_TO_REG;
                    else if (op1.type === "register" && op2.type === "regaddress")
                        opCode = OpCodes.ADD_REGADDRESS_TO_REG;
                    else if (op1.type === "register" && op2.type === "address")
                        opCode = OpCodes.ADD_ADDRESS_TO_REG;
                    else if (op1.type === "register" && op2.type === "number")
                        opCode = OpCodes.ADD_NUMBER_TO_REG;
                    else
                        throw "ADD does not support this operands";

                    code.push(opCode, op1.value, op2.value);

                    break;
                case ...
                case ...
                default:
                    throw "Not a valid instruction: " + instr;
            }
        }
    }

    return code;
};

完整代码在asm.js

用户界面

用户界面,我推荐使用一种JavaScript框架。这会使得工作变得更容易。这里我们选择Angular。

我们使用两列的设计来展示模拟器。左边的列包含汇编代码输入和运行/停止键。右边的列包含CPU寄存器和标志位,内存,以及控制台输出。内存部分可以直接看到。这里写图片描述
主要的用户界面元素是run/stop, step 和reset键。这些按钮控制模拟器。运行键会调用汇编器来生成和装载代码到内存,用一个计时器来运行一个CPU周期。stop键停止CPU周期计时器。step键使得用户可以逐步执行CPU周期来debug。最后一个reset键可以把整个模拟器重置到初始态。

HTML
<button ng-click="runOrStopSimulator()">{{ simulatorTimer && 'Stop' || 'Run' }}</button>  
<button ng-click="runSimulatorOneStep()">Step</button>  
<button ng-click="resetSimulator()">Reset</button> 
JS Code:
var simulatorTimer = undefined;  
var simulatorClockSpeed = 300;

function runOrStopSimulator() {  
    if (!assembler.isAssembled) {
        assembler.run();
    }

    simulatorTimer = setInterval(runSimulatorOneStep, simulatorClockSpeed);
};

function runSimulatorOneStep() {  
    if (!assembler.isAssembled) {
        assembler.run();
    }

    cpu.step();
};

function resetSimulator() {  
    if (simulatorTimer !== undefined) {
        clearInterval(simulatorTimer);
    }

    simulatorTimer = undefined;
    cpu.reset();
    memory.reset();
};

下面的代码包含一个显示内存的简化版本代码。Angular之美在于,内存可以直接和用户界面表示绑定,所有内存的变化可以实时在用户界面显示出来。

<div class="console">  
  <div style="float:left;" class="console-character"
       ng-repeat="m in memory | startFrom: 232 track by $index">
    <span>{{ convertToChar(m) }}</span>
   </div>
</div>  
<div class="memory"  
     ng-repeat="m in memory track by $index">
  <div ng-class="getMemoryCellCss($index)" 
       ng-switch="isInstruction($index)">
    <small ng-switch-default>{{ m | number:displayHex }}</small>
    <a ng-switch-when="true" ng-click="jumpToLine($index)">
      <small>{{ m | number:displayHex }}</small>
    </a>
  </div>
</div>  

完整代码在index.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值