Máy ảo 8 bit


31

Lý lịch

Tôi thích con chip 6502 8 bit cũ của tôi. Thật thú vị khi giải quyết một số thách thức ở đây trên PPCG bằng mã máy 6502. Nhưng một số thứ nên đơn giản (như, đọc trong dữ liệu hoặc xuất ra thiết bị xuất chuẩn) thì không cần thiết phải cồng kềnh trong mã máy. Vì vậy, có một ý tưởng sơ bộ trong đầu tôi: Phát minh ra máy ảo 8 bit của riêng tôi lấy cảm hứng từ 6502, nhưng với thiết kế được sửa đổi để có thể sử dụng nhiều hơn cho các thách thức. Bắt đầu thực hiện một cái gì đó, tôi nhận ra đây có thể là một thử thách thú vị nếu thiết kế của VM bị giảm đến mức tối thiểu :)

Bài tập

Triển khai máy ảo 8 bit tuân theo thông số kỹ thuật sau. Đây là , vì vậy việc thực hiện với ít byte nhất sẽ thắng.

Đầu vào

Việc triển khai của bạn cần có các đầu vào sau:

  • Một byte không dấu duy nhất pc, đây là bộ đếm chương trình ban đầu (địa chỉ trong bộ nhớ nơi VM bắt đầu thực thi, 0dựa trên cơ sở)

  • Danh sách các byte có độ dài 256mục nhập tối đa , đây là RAM cho máy ảo (có nội dung ban đầu)

Bạn có thể lấy đầu vào này ở bất kỳ định dạng hợp lý.

Đầu ra

Danh sách các byte là nội dung cuối cùng của RAM sau khi VM kết thúc (xem bên dưới). Bạn có thể giả sử bạn chỉ nhận được đầu vào dẫn đến chấm dứt cuối cùng. Bất kỳ định dạng hợp lý đều được cho phép.

CPU ảo

CPU ảo có

  • bộ đếm chương trình 8 bit,
  • một thanh ghi tích lũy 8 bit được gọi A
  • một thanh ghi chỉ mục 8 bit được gọi X.

Có ba cờ trạng thái:

  • Z - cờ zero được đặt sau khi một số kết quả hoạt động trong 0
  • N - cờ âm được đặt sau khi một số thao tác dẫn đến số âm (ví dụ: bit 7 của kết quả được đặt)
  • C - cờ thực hiện được đặt bằng các bổ sung và dịch chuyển cho bit "bị thiếu" của kết quả

Khi bắt đầu, tất cả các cờ được xóa, bộ đếm chương trình được đặt thành một giá trị nhất định và nội dung của AXkhông xác định.

Các giá trị 8 bit đại diện cho một trong hai

  • một số nguyên không dấu trong phạm vi[0..255]
  • một số nguyên đã ký , bổ sung 2, trong phạm vi[-128..127]

tùy thuộc vào bối cảnh. Nếu một hoạt động vượt quá hoặc dưới mức, giá trị sẽ bao quanh (và trong trường hợp bổ sung, cờ mang bị ảnh hưởng).

Chấm dứt

Máy ảo chấm dứt khi

  • Một HLThướng dẫn đã đạt được
  • Một địa chỉ bộ nhớ không tồn tại được truy cập
  • Bộ đếm chương trình chạy bên ngoài bộ nhớ (lưu ý rằng nó không bao quanh ngay cả khi VM được cung cấp đầy đủ 256 byte bộ nhớ)

Chế độ địa chỉ

  • ngầm định - hướng dẫn không có đối số, toán hạng được ngụ ý
  • ngay lập tức - toán hạng là byte trực tiếp sau lệnh
  • tương đối - (chỉ dành cho phân nhánh) byte sau khi lệnh được ký (phần bù 2) và xác định phần bù để thêm vào bộ đếm chương trình nếu lấy nhánh. 0là vị trí của hướng dẫn sau
  • tuyệt đối - byte sau lệnh là địa chỉ của toán hạng
  • được lập chỉ mục - byte sau lệnh cộng X(thanh ghi) là địa chỉ của toán hạng

Hướng dẫn

Mỗi lệnh bao gồm một opcode (một byte) và, trong các chế độ địa chỉ ngay lập tức , tương đối , tuyệt đốiđược lập chỉ mục một byte đối số thứ hai. Khi CPU ảo thực thi một lệnh, nó sẽ tăng bộ đếm chương trình tương ứng (bằng 1hoặc 2).

Tất cả các opcodes hiển thị ở đây là trong hex.

  • LDA - tải toán hạng vào A

    • Opcodes: ngay lập tức : 00, tuyệt đối : 02, được lập chỉ mục:04
    • Flags: Z,N
  • STA- lưu trữ Avào toán hạng

    • Opcodes: ngay lập tức : 08, tuyệt đối : 0a, được lập chỉ mục:0c
  • LDX - tải toán hạng vào X

    • Opcodes: ngay lập tức : 10, tuyệt đối : 12, được lập chỉ mục:14
    • Flags: Z,N
  • STX- lưu trữ Xvào toán hạng

    • Opcodes: ngay lập tức : 18, tuyệt đối : 1a, được lập chỉ mục:1c
  • AND- bitwise của Avà toán hạng thànhA

    • Opcodes: ngay lập tức : 30, tuyệt đối : 32, được lập chỉ mục:34
    • Flags: Z,N
  • ORA- bitwise hoặc của Avà toán hạng thànhA

    • Opcodes: ngay lập tức : 38, tuyệt đối : 3a, được lập chỉ mục:3c
    • Flags: Z,N
  • EOR- bitwise xor (độc quyền hoặc) của Avà toán hạng thànhA

    • Opcodes: ngay lập tức : 40, tuyệt đối : 42, được lập chỉ mục:44
    • Flags: Z,N
  • LSR - dịch chuyển logic sang phải, dịch chuyển tất cả các bit của toán hạng một vị trí sang phải, bit 0 sẽ mang theo

    • Opcodes: ngay lập tức : 48, tuyệt đối : 4a, được lập chỉ mục:4c
    • Flags: Z, N,C
  • ASL - dịch chuyển số học sang trái, dịch chuyển tất cả các bit của toán hạng một vị trí sang trái, bit 7 sẽ mang theo

    • Opcodes: ngay lập tức : 50, tuyệt đối : 52, được lập chỉ mục:54
    • Flags: Z, N,C
  • ROR - xoay phải, dịch chuyển tất cả các bit của toán hạng một vị trí sang phải, chuyển sang bit 7, bit 0 chuyển sang mang

    • Opcodes: ngay lập tức : 58, tuyệt đối : 5a, được lập chỉ mục:5c
    • Flags: Z, N,C
  • ROL - xoay trái, dịch chuyển tất cả các bit của toán hạng một vị trí sang trái, chuyển sang bit 0, bit 7 chuyển sang mang

    • Opcodes: ngay lập tức : 60, tuyệt đối : 62, được lập chỉ mục:64
    • Flags: Z, N,C
  • ADC- thêm với carry, toán hạng cộng với carry được thêm vào A, carry được đặt khi tràn

    • Opcodes: ngay lập tức : 68, tuyệt đối : 6a, được lập chỉ mục:6c
    • Flags: Z, N,C
  • INC - toán hạng tăng thêm một

    • Opcodes: ngay lập tức : 78, tuyệt đối : 7a, được lập chỉ mục:7c
    • Flags: Z,N
  • DEC - toán hạng giảm dần bởi một

    • Opcodes: ngay lập tức : 80, tuyệt đối : 82, được lập chỉ mục:84
    • Flags: Z,N
  • CMP- so sánh Avới toán hạng bằng cách trừ toán hạng từ A, quên kết quả. Thực hiện được xóa trên dòng chảy, thiết lập khác

    • Opcodes: ngay lập tức : 88, tuyệt đối : 8a, được lập chỉ mục:8c
    • Flags: Z, N,C
  • CPX- so sánh X- tương tự như CMPđối vớiX

    • Opcodes: ngay lập tức : 90, tuyệt đối : 92, được lập chỉ mục:94
    • Flags: Z, N,C
  • HLT - chấm dứt

    • Opcodes: ẩn: c0
  • INX- tăng thêm Xmột

    • Opcodes: ẩn: c8
    • Flags: Z,N
  • DEX- giảm Xbởi một

    • Opcodes: ẩn: c9
    • Flags: Z,N
  • SEC - đặt cờ mang

    • Opcodes: ẩn: d0
    • Cờ: C
  • CLC - xóa cờ mang

    • Opcodes: ẩn: d1
    • Cờ: C
  • BRA - Chi nhánh luôn.

    • Opcodes: tương đối: f2
  • BNE- nhánh nếu Zxóa cờ

    • Opcodes: tương đối: f4
  • BEQ- nhánh nếu Zđặt cờ

    • Opcodes: tương đối: f6
  • BPL- nhánh nếu Nxóa cờ

    • Opcodes: tương đối: f8
  • BMI- nhánh nếu Nđặt cờ

    • Opcodes: tương đối: fa
  • BCC- nhánh nếu Cxóa cờ

    • Opcodes: tương đối: fc
  • BCS- nhánh nếu Cđặt cờ

    • Opcodes: tương đối: fe

Mã nguồn

Hành vi của VM không được xác định nếu tìm thấy bất kỳ opcode nào không ánh xạ tới một hướng dẫn hợp lệ từ danh sách trên.

Theo yêu cầu của Jonathan Allan , bạn có thể chọn bộ opcodes của riêng mình thay vì opcodes được hiển thị trong phần Hướng dẫn . Nếu bạn làm như vậy, bạn phải thêm một ánh xạ đầy đủ vào các opcode được sử dụng ở trên trong câu trả lời của bạn.

Ánh xạ phải là một tệp hex với các cặp <official opcode> <your opcode>, ví dụ: nếu bạn thay thế hai opcodes:

f4 f5
10 11

Dòng mới không quan trọng ở đây.

Các trường hợp thử nghiệm (opcodes chính thức)

// 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

Tôi có thể thêm nhiều testcase sau.

Tham khảo và thử nghiệm

Để trợ giúp với các thử nghiệm của riêng mình, đây là một số triển khai tham chiếu (hoàn toàn không được đánh gôn) - nó có thể xuất thông tin theo dõi (bao gồm các hướng dẫn tháo rời) sang stderrvà chuyển đổi các opcodes trong khi chạy.

Cách được đề xuất để có được nguồn:

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

Hoặc kiểm tra chi nhánh challengevà thực hiện git submodule update --init --recursivesau khi nhân bản, để có được hệ thống xây dựng tùy chỉnh của tôi.

Xây dựng công cụ với GNU make (chỉ cần gõ makehoặc gmakenếu trên hệ thống của bạn, mặc định make không phải là GNU make).

Cách sử dụng :gvm [-s startpc] [-h] [-t] [-c convfile] [-d] [-x] <initial_ram

  • -s startpc - bộ đếm chương trình ban đầu, mặc định là 0
  • -h - đầu vào ở dạng hex (nếu không là nhị phân)
  • -t - thực hiện theo dõi để stderr
  • -c convfile - chuyển đổi opcodes theo một ánh xạ được đưa ra trong convfile
  • -d - kết xuất bộ nhớ dưới dạng dữ liệu nhị phân
  • -x - kết xuất bộ nhớ dưới dạng hex
  • initial_ram - nội dung RAM ban đầu, dưới dạng hex hoặc nhị phân

Lưu ý tính năng chuyển đổi sẽ thất bại trên các chương trình sửa đổi opcodes trong khi chạy.

Tuyên bố từ chối trách nhiệm: Các quy tắc và thông số kỹ thuật ở trên là có thẩm quyền cho thử thách, không phải công cụ này. Điều này đặc biệt áp dụng cho tính năng chuyển đổi opcode. Nếu bạn nghĩ rằng công cụ được trình bày ở đây có lỗi ghi thông số kỹ thuật, vui lòng báo cáo trong một nhận xét :)


1
Tôi tưởng tượng rằng có lẽ sẽ có nhiều cơ hội chơi gôn bằng cách chọn các opcodes khác nhau cho các hướng dẫn nhưng có vẻ như các opcode đã được sửa (mặc dù bộ hướng dẫn là định nghĩa của máy). Có lẽ nên cân nhắc việc cho phép triển khai để có trang mã riêng?
Jonathan Allan

1
@Jonathan ALLan đã nghĩ hai lần về nó, tôi cho phép ngay bây giờ và có thể thêm một công cụ "chuyển đổi" để thực hiện các giải pháp sử dụng các bộ mã khác dễ dàng kiểm tra được.
Felix Palmen

1
@Arnauld btw lý do của tôi cho phép điều này là để giảm số lượng các trường hợp đặc biệt, vì vậy nó sẽ là "golfable" tốt hơn - mỗi opcode là ẩn, một nhánh tương đối hoặc cho phép cả ba chế độ địa chỉ khác :)
Felix Palmen

1
nếu BRA("nhánh luôn") không giới thiệu một nhánh trong luồng điều khiển, nó có nên được gọi JMPkhông?
ngn

1
@ngn, BRAtồn tại trong các thiết kế chip sau này (6502 không có hướng dẫn như vậy) như 65C02 và MC 68000. cũng JMPtồn tại. Sự khác biệt là BRAsử dụng địa chỉ tương đối và JMPsử dụng địa chỉ tuyệt đối. Vì vậy, tôi chỉ làm theo những thiết kế này - thực sự, nó không có vẻ hợp lý lắm;)
Felix Palmen

Câu trả lời:


16

C (gcc) , 1381 1338 1255 1073 byte

Cải thiện rất lớn nhờ Barecat và 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]();}

Hãy thử trực tuyến!

Rất nhiều định nghĩa được chuyển sang cờ trình biên dịch.

Giải thích (RẤT vô dụng):

#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] ();
}

Công việc tuyệt vời, ngay lập tức +1, thực sự không mong đợi giải pháp C trước :) việc nối thêm 00byte của bạn có thể hơi bẻ cong các quy tắc mặc dù ... Tôi thừa nhận tôi đã không thử phân tích mã này ... bạn có thể lưu byte làm I / O trong nhị phân thay vì hex? Sẽ được cho phép bởi các quy tắc :)
Felix Palmen

Tôi muốn thay thế quy tắc rằng các opcodes bất hợp pháp dẫn đến chấm dứt bằng cách chỉ nói rằng hành vi của các opcode bất hợp pháp là không xác định ... điều này sẽ làm tổn thương câu trả lời của bạn hay bạn sẽ ổn với điều đó?
Felix Palmen

@FelixPalmen tốt, miễn là chấm dứt hoàn toàn hợp lệ "undefined" hành vi, nó sẽ không làm tổn thương (nó sẽ mở ra một triển vọng mới cho chơi golf nó xuống thay thế!)
Max Yekhlakov

@MaxYekhlakov do "làm tổn thương" Tôi có nghĩa là không công bằng đối với giải pháp của bạn bởi vì bạn có thể "đã sử dụng byte" để đảm bảo một opcode bất hợp pháp chấm dứt vm. Tôi rất vui vì bạn hoan nghênh sự thay đổi quy tắc như một cơ hội :) Và một lần nữa, xin chúc mừng, tôi chỉ muốn thấy một giải pháp trong C, đó là ngôn ngữ lập trình yêu thích mọi thời đại của tôi. Thật đáng tiếc, bạn sẽ hiếm khi chiến thắng một thử thách chơi gôn ở C - tuy nhiên, "chơi gôn" C rất tuyệt :)
Felix Palmen

Bạn có thể thêm phần cờ để đăng?
l4m2

8

APL (Dyalog Classic) , 397 332 330 byte

cảm ơn @ Adám cho -8 byte

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←⍵}

Hãy thử trực tuyến!

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


Liệu giải pháp này có opcodes ngoài ý muốn và nếu không, bạn đã dành byte để tránh chúng? Xem bình luận này vì lý do tôi đang hỏi ...
Felix Palmen

@FelixPalmen Bây giờ bạn đề cập đến nó, vâng :( Ban đầu tôi đã tuân thủ quy tắc đó, nhưng khi tôi chơi golf, tôi đã vô tình tạo ra 4, 5, và có thể những người khác, các opcode hợp lệ.
ngn

2
thực hiện ngay bây giờ, tôi nhận ra rằng đó không phải là quyết định tốt nhất ngay từ đầu và @MaxYekhlakov không may không phải nói bất cứ điều gì về thay đổi quy tắc.
Felix Palmen

Bạn có cần f←không
Erik the Outgolfer

8

C (gcc) , 487 , 480 , 463 , 452 , 447 , 438 byte

Sử dụng ánh xạ hướng dẫn này . Bản cập nhật cho các hướng dẫn đã loại bỏ 9 byte và có khả năng nhiều hơn trong tương lai. Trả về bằng cách sửa đổi bộ nhớ được trỏ đến bởi đối số đầu tiên ( M). Cảm ơn @ceilingcat đã loại bỏ một số byte.

Phải được biên dịch bằng cờ -DO=*o -DD=*d -DI(e,a,b)=if(e){a;}else{b;} -Du="unsigned char"(đã được bao gồm trong byte).

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)}}

Hãy thử trực tuyến!

Tiền xử lý

-DO=*o -DD=*d

Hai cái này cung cấp một cách ngắn hơn để hủy đăng ký con trỏ.

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

Giảm số lượng byte cần thiết cho khai báo if-elses và kiểu.

Dưới đây là phiên bản mã có thể đọc được của con người, với các chỉ thị tiền xử lý được mở rộng và các biến được đổi tên thành dễ đọc.

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;
    }
  }
}

Hướng dẫn

Các hướng dẫn được cấu trúc như sau:

  • Các bit 6-7 biểu thị mức độ cao của hướng dẫn ( 00Nullary, 01Unary, 10Binary, 11Binary)

  • Bits 0-2 xác định toán hạng (s): R=0chọn AR=1chọn X. OP=00sử dụng thanh ghi làm toán hạng, OP=01chọn toán hạng ngay lập tức, OP=10chọn toán hạng tuyệt đối và OP=11chọn toán hạng được lập chỉ mục.

    • Như bạn có thể nhận thấy, điều này cho phép mọi thao tác được thực hiện trên một trong hai thanh ghi (mặc dù bạn vẫn chỉ có thể lập chỉ mục từ X) ngay cả khi chúng không thể được sử dụng theo thông số kỹ thuật. Ví dụ INC A, ADC X, 10ASL Xtất cả công việc.
  • Các bit 3-5 xác định điều kiện để phân nhánh: có một trong các bit cho biết cờ nào sẽ kiểm tra (bit 3-> C, bit 4-> N, bit 5-> Z). Nếu chỉ có một bit được đặt, hướng dẫn kiểm tra cho một cờ được thiết lập. Để kiểm tra cờ unset, lấy phần bù của các bit. Ví dụ 110các bài kiểm tra cho unset carry và 001cho set carry. 111000chi nhánh luôn.

  • Bạn cũng có thể phân nhánh đến một địa chỉ bù được lưu trong một thanh ghi, cho phép bạn viết các hàm hoặc bạn có thể sử dụng các chế độ lập chỉ mục tiêu chuẩn. OP=01hành xử như chi nhánh đặc điểm kỹ thuật.

+-----+----------+-------+-----------------------------------------------+
| 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 byte

Đưa đầu vào là (memory)(program_counter), nơi memorylà mộtUint8Array . Đầu ra bằng cách sửa đổi mảng này.

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(_)}

NB: Mã được nén bằng RegPack và chứa rất nhiều ký tự không thể in được, tất cả đều được thoát trong phần trình bày ở trên của nguồn.

Hãy thử trực tuyến!

Ánh xạ mã hóa và các trường hợp thử nghiệm

Máy ảo sử dụng ánh xạ opcode này .

Dưới đây là các trường hợp thử nghiệm được dịch, cùng với các đầu ra dự kiến.

Trường hợp thử nghiệm số 1

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

Sản lượng dự kiến:

09 20 32 01 44 55 fb

Trường hợp thử nghiệm # 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

Sản lượng dự kiến:

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

Trường hợp thử nghiệm # 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

Sản lượng dự kiến:

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

Giải nén và định dạng

Do mã được nén bằng thuật toán thay thế các chuỗi lặp lại thường xuyên bằng một ký tự duy nhất, nên sử dụng cùng một khối mã nhiều lần hơn là xác định và gọi các hàm trợ giúp hoặc lưu trữ các kết quả trung gian (như M[A]) trong các biến bổ sung.

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]
  )
}

Ấn tượng :) Không phải là một pro pro, vì vậy - đây có phải là chỉ mục vào một số "mảng mã" theo giá trị của opcode không? có vẻ tốt đẹp. Nhưng nếu o = ...dòng này được thực thi cho mọi lệnh, thì điều này có thể có "opcodes ngoài ý muốn"?
Felix Palmen

2
Tôi có lẽ nên thêm một trường hợp thử nghiệm: o Bây giờ, tôi nghĩ sẽ tốt hơn nếu cho phép các mã không mong muốn ... kiểm tra tính hợp lệ chỉ lãng phí byte ở đây, nhưng có lẽ đã quá muộn để thay đổi quy tắc :(
Felix Palmen

Chà, tôi đã định đề xuất chính xác điều đó, vì nó không thêm nhiều thách thức và bây giờ rất khó kiểm tra với ánh xạ tùy chỉnh. Nhưng có lẽ bạn nên hỏi / cảnh báo @MaxYekhlakov trước, vì họ có thể đã thực hiện đúng quy tắc.
Arnauld

c = M[A] >> 7 & 1<- là &1thực sự cần thiết ở đây?
Felix Palmen

2
Tôi khá chắc chắn rằng trình của bạn là một chức năng dù sao đi nữa, từ ngữ của tôi là "một danh sách các byte [...] bất kỳ định dạng hợp lý nào" và Uint8Arraythực sự chỉ gói gọn một danh sách các byte như vậy. Vì vậy, nếu đặt các byte trong một chuỗi hex là một cách đại diện có thể chấp nhận được cho đầu vào, tại sao nên đặt chúng vào một đối tượng chứa ...
Felix Palmen

2

PHP, 581 585 555 535 byte (không cạnh tranh)

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);

lấy mã PC và OP làm số nguyên cơ bản 10 từ các đối số dòng lệnh,
in bộ nhớ làm danh sách [base 10 address] => base 10 value.

Điều này chưa được kiểm tra kỹ lưỡng ; nhưng có một sự cố .

các bản đồ mã ở đây và ở đây là một tổng quan về ánh xạ của tôi:

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)

lưu ý phụ:
24kết quả trong một BNV(nhánh never = 2 byte NOP);
04, 08, 0CLà bí danh cho INX, CLCSEC
và bất cứ điều gì ở trên 3Fhoặc là một byte hai NOPhoặc một bí danh cho các hướng dẫn chế độ duy nhất.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.