8位虚拟机


31

背景

我喜欢旧的8位6502芯片。在6502机器代码中解决PPCG方面的一些挑战甚至很有趣。但是,一些应该很简单的事情(例如,读入数据或输出到stdout)在机器代码中不必要地麻烦。因此,我想到一个粗略的想法:发明我自己的8位虚拟机,该虚拟机受6502的启发,但修改后的设计使其更能应对挑战。开始实施某些东西时,我意识到如果将VM的设计减少到最低限度,这本身就是一个不错的挑战:)

任务

实现符合以下规范的8位虚拟机。这是,因此以最少的字节数实现。

输入值

您的实现应采用以下输入:

  • 一个无符号字节pc,它是初始程序计数器(0基于VM的内存开始执行的内存中的地址)

  • 具有最大256条目长度的字节列表,这是虚拟机的RAM(及其初始内容)

您可以采用任何合理的格式输入此信息。

输出量

字节列表,这是VM终止后RAM的最终内容(请参阅下文)。您可以假设您只得到导致最终终止的输入。允许使用任何明智的格式。

虚拟CPU

虚拟CPU具有

  • 一个8位程序计数器
  • 一个8位累加器寄存器称为A
  • 一个称为的8位索引寄存器X

有三个状态标志:

  • Z -在执行某些操作后置零标志 0
  • N -在某些操作导致负数之后设置负标志(现在将结果的第7位设置为1)
  • C -进位标志由加法设置,并移位结果的“缺失”位

在开始的标志被全部清零,程序计数器设置为给定值和内容A,并X是不确定的。

8位值代表

  • 范围内的无符号整数[0..255]
  • 符号整数(2的补码),范围为[-128..127]

取决于上下文。如果某个操作上溢或下溢,则该值会回绕(如果加法,进位标志也会受到影响)。

终止

虚拟机在以下时间终止

  • HLT到达指令
  • 访问了不存在的内存地址
  • 程序计数器在内存外部运行(请注意,即使VM被提供了完整的256字节内存,它也不会回绕)

地址模式

  • 隐式 -指令没有参数,隐含操作数
  • 立即数-操作数是指令之后的字节
  • 相对 -(仅用于分支)指令后的字节被签名(2的补码),并确定执行分支后要添加到程序计数器的偏移量。0是以下指令的位置
  • 绝对 -指令后的字节是操作数的地址
  • 索引 -指令加号X(寄存器)之后的字节是操作数的地址

使用说明

每条指令都由一个操作码(一个字节)组成,在寻址模式下是即时相对绝对索引第二个参数字节。当虚拟CPU执行一条指令时,它会相应地(由12)递增程序计数器。

此处显示的所有操作码均为十六进制。

  • LDA -将操作数加载到 A

    • 操作码:立即:00,绝对:02,索引:04
    • 标志:ZN
  • STA-存储A到操作数

    • 操作码:立即:08,绝对:0a,索引:0c
  • LDX -将操作数加载到 X

    • 操作码:立即:10,绝对:12,索引:14
    • 标志:ZN
  • STX-存储X到操作数

    • 操作码:立即:18,绝对:1a,索引:1c
  • AND-按位A和操作数并入A

    • 操作码:立即:30,绝对:32,索引:34
    • 标志:ZN
  • ORA-按位A和操作数并入A

    • 操作码:立即:38,绝对:3a,索引:3c
    • 标志:ZN
  • EOR-按位XOR(异或)的A和操作数到A

    • 操作码:立即:40,绝对:42,索引:44
    • 标志:ZN
  • LSR -逻辑右移,将操作数的所有位右移一位,位0进位

    • 操作码:立即:48,绝对:4a,索引:4c
    • 标志:ZNC
  • ASL -算术左移,将操作数的所有位左移一位,第7位进位

    • 操作码:立即:50,绝对:52,索引:54
    • 标志:ZNC
  • ROR -向右旋转,将操作数的所有位向右移一位,进位到位7,位0进位

    • 操作码:立即:58,绝对:5a,索引:5c
    • 标志:ZNC
  • ROL -向左旋转,将操作数的所有位向左移一位,进位转到位0,位7进位

    • 操作码:立即:60,绝对:62,索引:64
    • 标志:ZNC
  • ADC-带进位加法,操作数加进位加法A,进位设置为溢出

    • 操作码:立即:68,绝对:6a,索引:6c
    • 标志:ZNC
  • INC -将操作数加1

    • 操作码:立即:78,绝对:7a,索引:7c
    • 标志:ZN
  • DEC -将操作数减一

    • 操作码:立即:80,绝对:82,索引:84
    • 标志:ZN
  • CMP- A通过从中减去操作数来与操作数比较A,忘记结果。溢出时清除进位,否则设置

    • 操作码:立即:88,绝对:8a,索引:8c
    • 标志:ZNC
  • CPX-比较X-同CMPX

    • 操作码:立即:90,绝对:92,索引:94
    • 标志:ZNC
  • HLT -终止

    • 操作码:隐式: c0
  • INX-加X

    • 操作码:隐式: c8
    • 标志:ZN
  • DEX-减X

    • 操作码:隐式: c9
    • 标志:ZN
  • SEC -设置进位标志

    • 操作码:隐式: d0
    • 标志: C
  • CLC -清除进位标志

    • 操作码:隐式: d1
    • 标志: C
  • BRA -总是分支

    • 操作码:相对: f2
  • BNE-如果Z清除标志则分支

    • 操作码:相对: f4
  • BEQ-如果Z设置了标志则分支

    • 操作码:相对: f6
  • BPL-如果N清除标志则分支

    • 操作码:相对: f8
  • BMI-如果N设置了标志则分支

    • 操作码:相对: fa
  • BCC-如果C清除标志则分支

    • 操作码:相对: fc
  • BCS-如果C设置了标志则分支

    • 操作码:相对: fe

操作码

如果发现任何操作码未映射到上述列表中的有效指令,则VM的行为是不确定的。

根据Jonathan Allan的要求,您可以选择自己的一组操作码,而不是“ 说明”部分中显示的操作码。如果这样做,则必须将完整的映射添加到答案中上面使用的操作码中。

映射应该是带有对的十六进制文件<official opcode> <your opcode>,例如,如果您替换了两个操作码:

f4 f5
10 11

换行在这里无关紧要。

测试用例(官方操作码)

// some increments and decrements
pc:     0
ram:    10 10 7a 01 c9 f4 fb
output: 10 20 7a 01 c9 f4 fb

// a 16bit addition
pc:     4
ram:    e0 08 2a 02 02 00 6a 02 0a 00 02 01 6a 03 0a 01
output: 0a 0b 2a 02 02 00 6a 02 0a 00 02 01 6a 03 0a 01

// a 16bit multiplication
pc:     4
ram:    5e 01 28 00 10 10 4a 01 5a 00 fc 0d 02 02 d1 6a 21 0a 21 02 03 6a 22 0a 22 52
        02 62 03 c9 f8 e6 c0 00 00
output: 00 00 00 00 10 10 4a 01 5a 00 fc 0d 02 02 d1 6a 21 0a 21 02 03 6a 22 0a 22 52
        02 62 03 c9 f8 e6 c0 b0 36

我可能会在以后添加更多的测试用例。

参考和测试

为了帮助自己进行实验,这里有一些(完全不是高尔夫)参考实现 -它可以stderr在运行时将跟踪信息(包括反汇编的指令)输出到并转换操作码。

推荐的获取来源的方式:

git clone https://github.com/zirias/gvm --branch challenge --single-branch --recurse-submodules

或结帐分支challengegit submodule update --init --recursive在克隆后进行操作,以获取我的自定义构建系统。

使用GNU make构建工具(只需输入make,或者gmake在您的系统上,默认的make不是GNU make)。

用法gvm [-s startpc] [-h] [-t] [-c convfile] [-d] [-x] <initial_ram

  • -s startpc -初始程序计数器,默认为 0
  • -h -输入为十六进制(否则为二进制)
  • -t -跟踪执行到 stderr
  • -c convfile -根据给出的映射转换操作码 convfile
  • -d -将结果内存转储为二进制数据
  • -x -将结果内存转储为十六进制
  • initial_ram -初始RAM内容,十六进制或二进制

请注意,转换功能将在运行时修改操作码的程序上失败。

免责声明:以上规则和规范仅代表挑战,而非此工具。这尤其适用于操作码转换功能。如果您认为此处提供的工具存在规范错误,请在评论中进行报告:)


1
我以为通过为指令选择不同的操作码可能会有很多打高尔夫球的机会,但是看来这些操作码是固定的(即使指令集定义了机器)。也许值得考虑允许实现具有自己的代码页?
乔纳森·艾伦

1
@JonathanAllan对此进行了三思,我现在允许它,并且可能会添加一个“转换”工具,以使使用其他操作码集的解决方案易于测试。
菲利克斯·帕尔姆

1
@Arnauld顺便说一句,允许这样做的理由是减少特殊情况的数量,因此应该更好地“适用”-每个操作码都是隐式的,相对分支或允许所有其他三种寻址方式:)
Felix Palmen

1
如果BRA(“总是分支”)没有在控制流中引入分支,是否不应该将其称为JMP
ngn

1
@ngn很好地BRA存在于诸如65C02和MC 68000之类的后续芯片设计中(6502没有这样的指令)JMP。区别在于BRA使用相对寻址和JMP绝对寻址。因此,我只是遵循了这些设计-确实,这听起来并不合逻辑;)
Felix Palmen

Answers:


16

C(gcc)1381 1338 1255 1073字节

由于吊顶猫和Rogem极大地改善了性能。

#include<stdio.h>
C*F="%02hhx ";m[256],p,a,x,z,n,c,e;g;*I(){R++p+m;}*A(){R*I()+m;}*X(){R*I()+m+x;}C*Q(){W(printf,m[g],1)exit(a);}C*(*L[])()={I,Q,A,Q,X,Q,Q,Q};l(C*i){R 254/p?*i=*L[m[p]&7]():*Q();}s(i){R 254/p?*L[m[p]&7]()=i:*Q();}q(){p>254?Q():++p;}C*y(){p+=e;}B(U,z)B(V,!z)B(_,n)B(BM,!n)B(BC,c)B(BS,!c)C*(*b[])()={Q,Q,y,Q,U,Q,V,Q,_,Q,BM,Q,BC,Q,BS,Q};j(){z=!l(&a);v}o(){s(a);}t(){z=!l(&x);n=x&H;}D(){s(x);}f(K(&=)h(K(|=)i(K(^=)J(E;c=e&1;z=!(e/=2);s(e);w}k(E;c=w;z=!e;s(e*=2);}T(E;g=e&1;z=!(e=e/2|H*!!c);c=g;s(e);w}M(E;g=w z=!(e=e*2|H*!!c);c=g;s(e);}N(E;z=!(a=g=a+e+!!c);c=g>>8%2;G}P(E;z=!~e;--p;s(g=e+1);G}u(E;g=e-1;z=!g;--p;s(g);G}r(E;g=a-e;z=!g;c=G}S(E;g=x-e;z=!g;c=G}Y(){z=!(x=g=1-m[p]%2*2);n=x&H;}Z(){c=~m[p]&1;}d(){p<255||Q();e=m[++p];b[m[p-1]&15]();}(*O[])()={j,o,t,D,Q,Q,f,h,i,J,k,T,M,N,Q,P,u,r,S,Q,Q,Q,Q,Q,Q,Y,Z,Q,Q,Q,d,d};main(){scanf(F,&p);W(scanf,&m[g],0)for(;;q())O[m[p]/8]();}

在线尝试!

许多定义移至编译器标志。

说明(VERY ungolfed):

#include<stdio.h>

// useful defines
#define C unsigned char
#define H 128 // highest bit
#define R return

// code generator for I/O
#define W(o,p,q)for(g=-1;++g<256&&((q)||!feof(stdin));)(o)(F,(p));

// code generator for branching instruction handlers
#define BB(q)(){(q)||Y();}

// frequent pieces of code
#define NA n=a&H;
#define NE n=e&H;
#define NG n=g&H;
#define E l(&e)

// printf/scanf template
C * F = "%02hhx ";

// global state: m=memory, pax=registers, znc=flags
// e and g are for temporaries and type conversions
C m[256],p,a,x,z,n,c,e;g;

// get the pointer to a memory location:
C * I() {R &m[++p];} // immediate
C * A() {R &m[m[++p]];} // absolute
C * X() {R &m[m[++p]+x];} // indexed

// terminate the VM (and dump memory contents)
C * Q() { W(printf,m[g],1) exit(a);}

// an array of functions for accessing the memory
// They either return the pointer to memory location
// or terminate the program.
C * (*L[])()={I,Q,A,Q,X,Q,Q,Q};

// load a byte from the memory into the variable pointed by i
// terminate the program if we cannot access the address byte
l (C * i) {return 254 / p ? *i = *L[m[p]&7] () : *Q ();}

// save a byte i to the memory
// terminate the program if we cannot access the address byte
s (C i) {return 254 / p ? *L[m[p]&7]() = i : *Q ();}

// advance the instruction pointer (or fail if we fall outside the memory)
q () {p > 254 ? Q () : ++p;}

// branch
C * Y() {p += e;}

// generated functions for conditional branches
C * BN BB(z)
C * BZ BB(!z)
C * BP BB(n)
C * BM BB(!n)
C * BC BB(c)
C * BS BB(!c)

// a list of branch functions
C * (*B[])() = {Q,Q,Y,Q,BN,Q,BZ,Q,BP,Q,BM,Q,BC,Q,BS,Q};

// Instruction handling functions

OA () {z = !l (&a); NA} // lda
OB () {s (a);} // sta
OC () {z = !l (&x); n = x & H;} // ldx
OD () {s (x);} // stx
OG () {E; z = !(a &= e); NA} // and
OH () {E; z = !(a |= e); NA} // ora
OI () {E; z = !(a ^= e); NA} // eor
OJ () {E; c = e & 1; z = !(e /= 2); s (e); NE} // lsr
OK () {E; c = NE; z = !e; s (e *= 2);} // asl
OL () {E; g = e & 1; z = !(e = e / 2 | H * !!c); c = g; s (e); NE} // ror
OM () {E; g = e & H; z = !(e = e * 2 | H * !!c); c = g; s (e); NE} // rol
ON () {E; z = !(a = g = a + e + !!c); c = !!(g & 256); NG} // adc
OP () {E; z = !~e; --p; s (g = e + 1); NG} // inc
OQ () {E; g = e - 1; z = !g; --p; s (g); NG} // dec
OR () {E; g = a - e; z = !g; c = NG} // cmp
OS () {E; g = x - e; z = !g; c = NG} // cpx
OY () {z = !(x = g = ~m[p] & 1 * 2 - 1); n = x & H;} // inx/dex
OZ () {c = ~m[p] & 1;} // sec/clc
Od () {p < 255 || Q (); e = m[++p]; B[m[p-1]&15] ();} // branching

// list of opcode handlers
(*O[]) () = {OA,OB,OC,OD,Q,Q,OG,OH,OI,OJ,OK,OL,OM,ON,Q,OP,OQ,OR,OS,Q,Q,Q,Q,Q,Q,OY,OZ,Q,Q,Q,Od,Od};

// main function
main ()
{
    // read the instruction pointer
    scanf (F, &p);

    // read memory contents
    W(scanf, &m[g], 0)

    // repeatedly invoke instruction handlers until we eventually terminate
    for (;; q())
        O[m[p]/8] ();
}

很棒的工作,即时+1,真的没想到首先00要有C解决方案:) 尽管您追加字节可能会使规则有些弯曲……我承认我没有尝试分析这段代码……您也许将字节做I / O的字节保存为二进制而不是十六进制?该规则将允许:)
菲利克斯·帕尔曼

我想通过说非法操作码的行为是不确定的来代替非法操作码导致终止的规则...这会伤害您的答案还是可以吗?
Felix Palmen '18

@FelixPalmen好吧,只要终止是相当有效的“未定义”行为,它就不会受到伤害(这为击倒高尔夫球带来了新的可能!)
Max Yekhlakov

@MaxYekhlakov by“ hurt”我的意思是对您的解决方案不公平,因为您可能为了确保非法操作码终止了vm而“花费了字节”。我很高兴您将规则的更改作为机会:)再一次,恭喜,我只是喜欢看到C语言的解决方案,这是我一直以来最喜欢的编程语言。遗憾的是,您很少会在C语言中赢得代码高尔夫挑战-尽管如此,“高尔夫” C真的很酷:)
Felix Palmen

您可以添加标志部分来发布吗?
l4m2

8

APL(雅致经典)397 332 330字节

感谢@Adám-8个字节

f←{q2a x c←≡B256⋄{0::m
⍺←(∇q∘←)0∘=,B≤+⍨
30u←⌊8÷⍨bpm:∇p+←129-B|127-1pm×⊃b2/(,~,⍪)1,q,c
p+←1
u=25:⍺x⊢←B|x1*b
u=26:∇c⊢←~2|b
p+←≢om⊃⍨i←⍎'p⊃m+x'↑⍨1+8|b
u⊃(,⍉'⍺ax⊢←o' '∇m[i]←ax'∘.~'xa'),5 4 2 3 2/'⍺⌽⊃'∘,¨'a⊢←2⊥(⍕⊃u⌽''∧∨≠'')/o a⊤⍨8⍴2' 'c(i⊃m)←u⌽d⊤(⌽d←u⌽2B)⊥u⌽o,c×u>10' 'c a⊢←2B⊤a+o+c' 'm[i]←B|o-¯1*u' 'c⊢←⊃2B⊤o-⍨⊃u⌽x a'}p m←⍵}

在线尝试!

p  program counter
m  memory
a  accumulator register
x  index register
q  flags z (zero) and n (negative) as a length-2 vector
c  flag for carry
  function to update z and n
b  current instruction
u  highest 5 bits of b
o  operand
i  target address in memory


此解决方案是否具有意外的操作码?如果没有,您是否花了字节避免它们?看到这个评论是出于我要问的原因...
Felix Palmen '18

@FelixPalmen现在,您提到它,是:(最初我遵守该规则,但是在打高尔夫球时,我不小心设置了4、5,甚至可能是其他有效的操作码。因此,最好决定将其行为定义为不确定:)
ngn

2
现在,我意识到这并不是一个最佳决定,@ MaxYekhlakov不必对规则更改说什么。
菲利克斯·帕尔姆


8

C(GCC) 487480463452447,438个字节

使用此指令映射。指令的更新减少了9个字节,将来可能还会更多。通过修改第一个参数(M)指向的内存来返回。感谢@ceilingcat削减了一些字节。

必须使用标志进行编译-DO=*o -DD=*d -DI(e,a,b)=if(e){a;}else{b;} -Du="unsigned char"(已包含在字节中)。

e(M,Z,C)u*M,C;{for(u r[2],S=0,D,O,c,t;o=C<Z?M+C++:0;){I(c=O,d=r+!(c&4),break)I(o=c&3?C<Z&&C?M+C++:0:d,o=c&2?O+c%2**r+M:o,break)t=(c/=8)&7;I(c<24&c>4&&t,t&=3;I(c&8,I(c&4,c&=S&1;S=O>>7*!(t/=2);O=t=O<<!t>>t|c<<7*t,t=O+=t%2*2-1),I(c&4,D=t=t?t&2?t&1?O^D:O|D:O&D:O,I(c&1,S=D>(t=D+=O+S%2),t=D-O;S=t>D)))S=S&1|t>>6&2|4*!t,I(c&8,C+=!(t&~-t?~t&S:t&~S)*O,I(t,S=S&6|c%2,O=D)))I(C,,Z=0)}}

在线尝试!

预处理器

-DO=*o -DD=*d

这两个提供了一种取消引用指针的较短方法。

-DI(e,a,b)=if(e){a;}else{b;} -Du="unsigned char"

减少if-elses和类型声明所需的字节数。

下面是该代码的人类可读版本,扩展了预处理程序指令,并重命名了变量以提高可读性。

exec_8bit(unsigned char *ram, int ramSize, unsigned char PC)
{  
  for(unsigned char reg[2], SR=0, // The registers. 
                                  // reg[0] is X, reg[1] is A. 
                                  // SR contains the flags.
      *dst, *op, opCode, tmp;
      // Load the next instruction as long as we haven't gone out of ram.
      op = PC < ramSize ? ram + PC++ : 0;)
  { // Check for HLT.
    if(opCode=*op)
    { // Take a pointer to the register selected by complement of bit 3.
      dst = reg+!(opCode&4);
    } else break;
    // Load operand as indicated by bits 0 and 1. Also check that we don't
    // go out of bounds and that the PC doesn't overflow.
    if(op = opCode&3 ? PC<ramSize && PC ? ram + PC++ : 0 : dst)
    {
      op = opCode&2 ? *op + opCode%2 * *reg + ram: op
    } else break;

    // Store the bits 3-5 in tmp.
    tmp = (opCode/=8) & 7;
    if(opCode<24 & opCode>4 && tmp)
    { // If not HLT, CLC, SEC or ST, enter this block.
      tmp &= 3; // We only care about bits 3&4 here.
      if(opCode&8) // Determine whether the operation is binary or unary.
      { // Unary
        if(opCode&4)
        { // Bitshift
          opCode &= SR&1; // Extract carry flag and AND it with bit 3 in opCode.
          SR=*op >> 7*!(tmp/=2);// Update carry flag.
          // Shift to left if bit 4 unset, to right if set. Inclusive-OR 
          // the carry/bit 3 onto the correct end.
          *op = tmp = *op << !tmp >> tmp | opCode << 7*tmp;
        } else tmp=*o+=tmp%2*2-1;
      } else if(opCode&4) {
        // Bitwise operations and LD.
        // Ternary conditional to determine which operation.
        *dst = tmp = tmp? tmp&2? tmp&1? *op^*dst: *op|*dst: *op&*dst: *op
      } else if(opCode&1) {
        // ADC. Abuse precedence to do this in fewer bytes.
        // Updates carry flag.
        SR = *dst > (tmp = *dst += *op + SR%2);
      } else tmp=*dst-*op; SR = tmp > *dst; // Comparison.
      SR = SR&1 | tmp >> 6&2 | 4*!tmp; // Update Z and N flags, leaving C as it is.
    } else if(opCode&8) {
      // Branch.
      // tmp&~-tmp returns a truthy value when tmp has more than one bit set
      // We use this to detect the "unset" and "always" conditions.
      // Then, we bitwise-AND either ~tmp&SR or tmp&~SR to get a falsy value
      // when the condition is fulfilled. Finally, we take logical complement,
      // and multiply the resulting value (`1` or `0`) with the operand,
      // and add the result to program counter to perform the jump.
      PC += !(tmp & ~-tmp? ~tmp&SR : tmp&~SR) * *op;
    } else if (tmp) { // SEC, CLC
      SR = SR&6 | opCode % 2;
    } else {
      *op = *dst; // ST
    }
    if(!PC){ // If program counter looped around, null out ramSize to stop.
           // There's likely a bug here that will kill the program when it
           // branches back to address 0x00
      ramSize=0;
    }
  }
}

使用说明

这些说明的结构如下:

  • 位6-7表示指令的稀疏性(00Nullary,01Unary,10Binary,11Binary)

  • 位0-2确定操作数:R=0选择AR=1选择XOP=00将寄存器用作操作数,OP=01选择立即数操作数,OP=10选择绝对操作数并OP=11选择索引操作数。

    • 您可能已经注意到,这允许对任一寄存器执行任何操作(尽管您仍然只能从进行索引X),即使通常无法按规范使用它们也是如此。例如INC AADC X, 10ASL X所有的工作。
  • 比特3-5确定分支的条件:其中一个比特指示要测试的标志(比特3-> C,比特4-> N,比特5-> Z)。如果只设置了一位,则指令将测试设置标志。要测试未设置的标志,请对这些位进行补码。例如,未套入110001套入的测试。111000总是分支。

  • 您还可以跳转到存储在寄存器中的地址偏移量,以允许您编写函数,或者可以使用标准索引模式。OP=01表现像规范分支。

+-----+----------+-------+-----------------------------------------------+
| OP  | BINARY   | FLAGS | INFO                                          |
+-----+----------+-------+-----------------------------------------------+
| ST  | 10000ROP |       | Register -> Operand                           |
| LD  | 10100ROP | Z N   | Operand -> Register                           |
| AND | 10101ROP | Z N   | Register &= Operand                           |
| XOR | 10111ROP | Z N   | Register ^= Operand                           |
| IOR | 10110ROP | Z N   | Register |= Operand                           |
| ADC | 10011ROP | Z N C | Register += Operand + Carry                   |
| INC | 01011ROP | Z N   | Operand += 1                                  |
| DEC | 01010ROP | Z N   | Operand -= 1                                  |
| ASL | 01100ROP | Z N C | Operand <<= 1                                 |
| LSR | 01110ROP | Z N C | Operand >>= 1                                 |
| ROL | 01101ROP | Z N C | Operand = Operand << 1 | Carry                |
| ROR | 01111ROP | Z N C | Operand = Operand >> 1 | Carry << 7           |
| CMP | 10010ROP | Z N C | Update ZNC based on Register - Operand        |
| BR  | 11CNDROP |       | PC += Condition ? Operand : 0      |
| SEC | 00011000 |     C | Set carry                                     |
| CLC | 00010000 |     C | Clear carry                                   |
| HLT | 00000000 |       | Halt execution.                               |
+-----+----------+-------+-----------------------------------------------+

7

JavaScript(ES6),361个字节

取输入作为(memory)(program_counter),其中memory是一个Uint8Array通过修改此数组的输出

M=>p=>{for(_='M[\u0011\u0011A]\u0010\u0010=\u000fc=\u000e,\u0011p]\f(n=\u000b128)\t=\u0010\b&=255\u0007,z=!(n\u0007),n&=\t;\u0006\u0006\u000b\u0005-\u0010,\u000en>=0\u0005\u0004\u0011c\b>>7,A]*2\u0005\u0003\u0011c\b&1,A]/2\u0005\u000f\u0002&&(p+=(\u0010^\t-\t;\u0001for(a=n=z=\u000ex=0;a\u0007,x\u0007,A=[i=\u0011p++],p\f\f+x][i&3],i&3&&p++,i&&A<256;)eval(`\u000ba\b\u0006\u000fa;\u000bx\b\u0006\u000fx;\u000ba&\b\u0005a|\b\u0005a^\b\u0005\u000f\u0002\u0003\u000fc*128|\u0002c|\u0003a+\b+c,\u000ea>>8\u0005++\u0010\u0005--\u0010\u0005a\u0004x\u0004++x\u0005--x\u0006\u000e1;\u000e0;1\u0001!z\u0001z\u0001!n\u0001n\u0001!c\u0001c\u0001`.split`;`[i>>2])';G=/[\u0001-\u0011]/.exec(_);)with(_.split(G))_=join(shift());eval(_)}

注意:该代码使用RegPack压缩,并且包含许多不可打印的字符,这些字符在上述源代码表示中均已转义。

在线尝试!

操作码映射和测试用例

虚拟机使用此操作码映射

以下是翻译的测试用例以及预期的输出。

测试用例#1

00 - LDX #$10  09 10
02 - INC $01   32 01
04 - DEX       44
05 - BNE $fb   55 fb

预期产量:

09 20 32 01 44 55 fb

测试案例#2

00 - (DATA)    e0 08 2a 02
04 - LDA $00   02 00
06 - ADC $02   2e 02
08 - STA $00   06 00
0a - LDA $01   02 01
0c - ADC $03   2e 03
0e - STA $01   06 01

预期产量:

0a 0b 2a 02 02 00 2e 02 06 00 02 01 2e 03 06 01

测试案例#3

00 - (DATA)    5e 01 28 00
04 - LDX #$10  09 10
06 - LSR $01   1e 01
08 - ROR $00   26 00
0a - BCC $0d   65 0d
0c - LDA $02   02 02
0e - CLC       4c
0f - ADC $21   2e 21
11 - STA $21   06 21
13 - LDA $03   02 03
15 - ADC $22   2e 22
17 - STA $22   06 22
19 - ASL $02   22 02
1b - ROL $03   2a 03
1d - DEX       44
1e - BPL $e6   5d e6
20 - HLT       00
21 - (DATA)    00 00

预期产量:

00 00 00 00 09 10 1e 01  44 5d e6 00 00

打开包装并格式化

由于代码是用一种算法压缩的,该算法用一个字符替换经常重复的字符串,因此一遍又一遍地使用相同的代码块比定义和调用辅助函数或将中间结果(例如M[A])存储在其他变量中更为有效。

M => p => {
  for(
    a = n = z = c = x = 0;
    a &= 255, x &= 255,
    A = [i = M[p++], p, M[p], M[p] + x][i & 3],
    i & 3 && p++,
    i && A < 256;
  ) eval((
    '(n = a = M[A], z = !(n &= 255), n &= 128);'                                + // LDA
    'M[A] = a;'                                                                 + // STA
    '(n = x = M[A], z = !(n &= 255), n &= 128);'                                + // LDX
    'M[A] = x;'                                                                 + // STX
    '(n = a &= M[A], z = !(n &= 255), n &= 128);'                               + // AND
    '(n = a |= M[A], z = !(n &= 255), n &= 128);'                               + // ORA
    '(n = a ^= M[A], z = !(n &= 255), n &= 128);'                               + // EOR
    '(n = M[A] = M[c = M[A] & 1, A] / 2, z = !(n &= 255), n &= 128);'           + // LSR
    '(n = M[A] = M[c = M[A] >> 7, A] * 2, z = !(n &= 255), n &= 128);'          + // ASL
    '(n = M[A] = c * 128 | M[c = M[A] & 1, A] / 2, z = !(n &= 255), n &= 128);' + // ROR
    '(n = M[A] = c | M[c = M[A] >> 7, A] * 2, z = !(n &= 255), n &= 128);'      + // ROL
    '(n = a += M[A] + c, c = a >> 8, z = !(n &= 255), n &= 128);'               + // ADC
    '(n = ++M[A], z = !(n &= 255), n &= 128);'                                  + // INC
    '(n = --M[A], z = !(n &= 255), n &= 128);'                                  + // DEC
    '(n = a - M[A], c = n >= 0, z = !(n &= 255), n &= 128);'                    + // CMP
    '(n = x - M[A], c = n >= 0, z = !(n &= 255), n &= 128);'                    + // CPX
    '(n = ++x, z = !(n &= 255), n &= 128);'                                     + // INX
    '(n = --x, z = !(n &= 255), n &= 128);'                                     + // DEX
    'c = 1;'                                                                    + // SEC
    'c = 0;'                                                                    + // CLC
    ' 1 && (p += (M[A] ^ 128) - 128);'                                          + // BRA
    '!z && (p += (M[A] ^ 128) - 128);'                                          + // BNE
    ' z && (p += (M[A] ^ 128) - 128);'                                          + // BEQ
    '!n && (p += (M[A] ^ 128) - 128);'                                          + // BPL
    ' n && (p += (M[A] ^ 128) - 128);'                                          + // BMI
    '!c && (p += (M[A] ^ 128) - 128);'                                          + // BCC
    ' c && (p += (M[A] ^ 128) - 128);')                                           // BCS
    .split`;`[i >> 2]
  )
}

令人印象深刻:)不是JS专业人士,所以-通过操作码的值将其索引到某些“代码数组”中吗?看起来不错。但是,如果o = ...对于每条指令都执行此行,则可能会有“意外操作码”吗?
菲利克斯·帕尔姆

2
我可能应该添加一个测试用例:o现在,我认为允许意外的操作码会更好。有效性检查只是在这里浪费字节,但是更改规则可能为时已晚:(
Felix Palmen

好吧,我正好建议这一点,因为它并没有增加太多挑战,现在很难使用自定义映射进行检查。但是您可能应该先询问/警告@MaxYekhlakov,因为他们可能已正确实施了该规则。
Arnauld

c = M[A] >> 7 & 1<- &1这里真的需要吗?
Felix Palmen '18

2
我很确定,无论如何您提交的内容都是一个函数,我的措辞是“任何明智格式的字节列表”,Uint8Array实际上只是封装了这样一个字节列表。因此,如果将字节放在十六进制字符串中是表示输入的可接受方式,为什么禁止将它们放入容器对象中……
Felix Palmen

2

PHP,581个585 555个 532字节(不-yet-竞争)

function t($v){global$f,$c;$f=8*$c|4&$v>>5|2*!$v;}$m=array_slice($argv,2);for($f=0;$p>-1&&$p<$argc-1&&$i=$m[$p=&$argv[1]];$p++)$i&3?eval(explode(_,'$a=$y_$a&=$y_$a|=$y_$a^=$y_$a+=$y+$c;$c=$a>>8_$x=$y_$c=$y&1;$y>>=1_$c=($y*=2)>>8_$y+=$y+$c;$c=$y>>8_$y+=$c<<8;$c=$y&1;$y>>=1_$y--_$y++_$z=$a-$y,$c=$a<$y_$z=$x-$y,$c=$x<$y_$y=$a_$y=$x_'.$y=&$m[[0,++$p,$g=$m[$p],$g+$x][$i&3]])[$i>>=2].'$i<14&&t(${[aaaaaxyyyyyyzz][$i]}&=255);'):($i&32?$p+=($f>>$i/8-4^$i)&1?($y=$m[++$p])-($y>>7<<8):1:($i&8?$f=$f&7|8*$c=$i/4:t($x+=$i/2-9)));print_r($m);

从命令行参数中将PC和OP代码作为基数10的整数,将
内存打印为的列表[base 10 address] => base 10 value

这还没有经过彻底的测试 ; 但有一个故障

代码映射 ,这是我的映射的概述:

3-mode instructions:
00: LDA     04: AND     08: ORA     0C: EOR
10: ADC     14: LDX     18: LSR     1C: ASL
20: ROL     24: ROR     28: DEC     2C: INC
30: CMP     34: CPX     38: STA     3C: STX

+1: immediate
+2: absolute
+3: relative

implicit:
00: HLT
10: DEX 14: INX
18: CLC 1C: SEC

relative:
20: BRA         (0)
28: BNE 2C: BEQ (Z)
30: BPL 34: BMI (N)
38: BCC 3C: BCS (C)

旁注:
代码24导致一个BNV(分支从不= 2个字节NOP);
04080C是别名INXCLCSEC
以上任何3F可以是一个两个字节NOP或为单模式指令的别名。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.