From 414d27fc43dfa824f92b4b7c6f073e251dbe18e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anton=20Luka=20=C5=A0ijanec?= Date: Wed, 19 Jan 2022 22:39:01 +0100 Subject: prvi commit --- .gitignore | 2 + Makefile | 36 +++++++++++++ main.cpp | 169 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ main.hpp | 151 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 358 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 main.cpp create mode 100644 main.hpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b379de5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.gdb_history +ov diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1d860b2 --- /dev/null +++ b/Makefile @@ -0,0 +1,36 @@ +DESTDIR=/ +SRCFILE=main.cpp +O=0 +CFLAGS += -Wextra -Wall -g -O$O -I. -oov -finput-charset=UTF-8 -fextended-identifiers +LIBS += +CC=c++ +VGARGS += --leak-check=full --track-origins=yes --verbose --log-file=valgrind-out.txt --suppressions=/usr/lib/i386-linux-gnu/valgrind/default.supp --show-leak-kinds=all --leak-resolution=high + +.NOTPARALLEL: +default: + $(CC) $(CFLAGS) $(SRCFILE) $(LIBS) + +# tests if code compiles under gcc, clang and tcc +cc: + make -e CC=tcc + make -e CC=gcc + make -e CC=clang + +install: + mkdir -p $(DESTDIR)/usr/bin/ + cp ov $(DESTDIR)/usr/bin/ + +distclean: + rm ov -f + +clean: + rm ov -f + +prepare: + apt install build-essential -y + +# developing is to be done on i386. for example the default suppression file is hardcoded for i386 here: +# developing is to be done on bullseye. + +valgrind: + G_SLICE=always-malloc G_DEBUG=gc-friendly valgrind $(VGARGS) ./ov diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..7254b1e --- /dev/null +++ b/main.cpp @@ -0,0 +1,169 @@ +#include /* src, dest, instr, padding */ +#include "main.hpp" +using namespace std; +using namespace ov; +namespace ov { + template + value_type Ram::peek (index_type addr) { + if (addr < ov->pas) + return ov->pc & 1 << addr; + if (addr >= (unsigned int) 1 << ov->ras) + return ov->opts[addr-(1 << ov->ras)]; + if (addr-ov->pas < IOLEN) + return ov->io[addr-ov->rs]; + return storage[addr]; + } + template + void Ram::poke (index_type addr, value_type val) { + if (addr < ov->pas) { + ov->pc &= ~(1 << addr); + if (val) + ov->pc |= 1 << addr; + } /* we fall through */ + if (addr >= (unsigned int) 1 << ov->ras) + ov->opts[addr-ov->rs] = val; + if (addr >= ov->pas && addr-ov->pas < IOLEN) { + if (ov->opts[BUFIN] && addr-ov->pas == INA && val == 0) { /* machine read */ + if (!ov->inbuf.empty()) { /* and wants more. oh, it looks like we */ + ov->io[IN] = ov->inbuf.front(); /* have more. we pop the */ + ov->inbuf.pop(); /* queue, set the input available bit and */ + ov->io[INA] = 1; /* return, so actual set will not be */ + return; /* applied, because input is ready. */ + } /* if we have no input, we just store the zero */ + } /* we do similar things for output. whenever the machine is ready to */ + if (ov->opts[BUFOUT] && addr-ov->pas == OUTA && val == 1) { /* output, it */ + ov->outbuf.push(ov->io[OUT]); /* sets OUTA bit. we push the OUT */ + ov->io[OUT] = 0; /* value into the output queue and clear OUTA bit */ + return; /* and therefore indicating the host has read the output. */ + } + ov->io[addr-ov->pas] = val; + } /* we fall through, but only if !BUF__ */ + storage[addr] = val; + } + template + Ram::Ram (Ov * ov) { + this->ov = ov; + storage.reserve(ov->rs+ov->pas); + for (unsigned int i = 0; i <= ov->rs+ov->pas; i++) + poke(i, 0); + } + template // inventor did not say about + Program::Program (Ov * ov) { // initializing program memory, so I + this->ov = ov; // do not do that. but by "spec", ram + storage.reserve(ov->ps); // is set to zero and code starts at + } // pc == 0, unless of course set with + struct NotImplemented : public exception { // Ov::pc. + const char * what () const throw () { + return "Not implemented."; + } + } NotImplemented; + struct NotAvailable : public exception { + const char * what () const throw () { + return "I/O is currently not available. You can try again."; + } + } NotAvailable; + struct BufferingRequired : public exception { + const char * what () const throw () { + return "I/O buffering must be enabled for this function, but it's not."; + } + } BufferingRequired; + struct NotAligned : public exception { + const char * what () const throw () { + return "Byte count can't be deserialized without remainder."; + } + } NotAligned; + void Ov::step () { // ko se izvaja inštrukcija, kaže števec programa na naslednjo. + bool b[pas]; // buffer. you have to first read all and then write for COPY + switch (pm(pc++).i) { // predstavljaj si, da so oklepaji okoli pc++ oglati (; + case COPY: + for (int i = 0; i < pas; i++) + b[i] = ram[pm(pc).s+i]; + for (int i = 0; i < pas; i++) + ram[pm(pc).d+i] = b[i]; + break; + case NAND: + ram[pm(pc).d] = !(ram[pm(pc).s] & ram[pm(pc).d]); + break; + } + } + bool Ov::out () { /* output from machine. throw NotAvailable when there's nothing */ + if (opts[BUFOUT]) { + if (outbuf.empty()) + throw NotAvailable; + bool o = outbuf.front(); + outbuf.pop(); /* unconveniently, pop returns nothing */ + return o; + } + if (!(io[OUTA])) + throw NotAvailable; + io[OUTA] = 0; + return io[OUT]; + } /* if BUFOUT is set in opts, this is read from the buffer instead */ + char Ov::outc () { /* output a byte from machine or throw NotAvailable if not enough bits */ + if (!opts[BUFOUT]) + throw BufferingRequired; + if (outbuf.size() < 8) + throw NotAvailable; + char r; + for (int i = 0; i < 8; i++) { // bitwise endianness is big + bool b = outbuf.front(); + r &= ~(1 << (8-i)); + if (b) + r |= 1 << (8-i); + outbuf.pop(); + } + return r; + } /* buffering must be enabled for this to work. */ + void Ov::in (bool i) { /* input to machine. thrw NotAvailable when program hasn't read */ + if (opts[BUFOUT]) { /* for BUFOUT input we ONLY insert into the queue if machine */ + if (io[INA]) /* "EWOULDBLOCK", in case it can read we put directly to io. */ + inbuf.push(i); + io[IN] = i; + io[INA] = 1; + return; + } + if (io[INA]) /* bit is set, so program in VM must first clear the bit. */ + throw NotAvailable; + io[IN] = i; + io[INA] = 1; + } /* if BUFIN is set in opts, this is put to the buffer instead */ + void Ov::inc (char i) { // input a byte to the machine + if (!opts[BUFIN]) + throw BufferingRequired; + if (inbuf.size() < 8) + throw NotAvailable; + for (int x = 7; x >= 0; x--) { + in(i & 1 << x); + } + } // buffering must be enabled for this to work. + struct instr Ov::deserialize (const char * c) { // treats i as array of is size + struct instr r; + for (int i = 0; i < ras; i++) + if (c[i/8] & 1 << (i%8-8)) + r.s |= 1 << i; + for (int i = 0; i < ras; i++) { + int j = i+ras; + if (c[j/8] & 1 << (j%8-8)) + r.d |= 1 << i; + } + r.i = c[is-1] & 1 << 6; + r.p = c[is-1] & 1 << 7; + return r; + } + void Ov::deserialize (istream & v = cin, unsigned int o = 0) { + string c((istreambuf_iterator(v)), istreambuf_iterator()); // eof + unsigned int s = c.size(); + if (s % 2) + throw NotAligned; + for (unsigned int i = 0; i < s; i += is) { + pm[o+i/s] = deserialize(c.c_str()+i); + } + } +} +int main (void /* int argc, char ** argv */) { + clog << "OV stands for OB (One Bit) VM (Virtual Machine)." << endl + << "Stanard input is ready to accept a binary." << endl; + Ov ov; + ov.deserialize(); + return 0; +} diff --git a/main.hpp b/main.hpp new file mode 100644 index 0000000..f587fab --- /dev/null +++ b/main.hpp @@ -0,0 +1,151 @@ +#include +#include +namespace ov { + using namespace std; + enum opts { // opts are pas-1 bits aftr last ram ptr (2**ras)-1 and can be w/r from prg + DEFAULT, + BUFOUT, // output from program in VM is always possible and no blocks + BUFIN, // input to program in VM is always possible and no blocks + BUFCLR, // clears whatever is in the buffer + OPTSLEN + }; // read those last bits with the COPY instruction with source/dest on last pointer + enum iobits { + INA, // input available, clear when read + IN, // input bit + OUTA, // output available, set when have output, cleared by VM when wrote + OUT, // output bit + IOLEN + }; + enum instrs { + COPY, + NAND + }; + struct instr { // deserializirana inštrukcija + unsigned int s = 0; // source + unsigned int d = 0; // destination + bool i = 0; // instruction + bool p = 0; // enobitni padding, lahko za metainštrukcije + }; // privzeto inicializiran na NOOP inštrukcijo + template class Mmu { + private: + class Cell { + private: + friend class Mmu; + Mmu & mmu; + index_type index; + Cell (Mmu & mmu, index_type index) + : mmu(mmu), index(index) {} + public: + operator value_type (void) { + return mmu.memory.peek(index); + } + value_type operator= (value_type value) { + mmu.memory.poke(index, value); + return value; + } + }; + public: + Memory & memory; + Mmu (Memory & memory) : memory(memory) {} + Cell operator[] (index_type index) { + return Cell(*this, index); + } + value_type & operator() (index_type index) { + return memory.peek(index); + } + }; + class Ov; + template class Ram { + private: + vector storage; + public: + Ov * ov; + value_type peek (index_type); + void poke(index_type, value_type); + Ram (Ov *); + }; + /* v Program (memory) bi lahko uporabili metainštrukcije (tisti padding bit) v + * vsaki inštrukciji in v metainštrukcijah reprezentirali assembly org (lokacijo). + * s tem bi lahko imeli npr. 128 biten program counter in s tem zelo preproste jumpe, + * ne bi pa bilo treba narediti 2^128 vektor in posledično binarno datoteko. + * Tak način bi bilo verjetno težko implementirati na dejanski strojni opremi, + * tukaj pa bi v enem passu čez cel deserializan program memory zaznali te org + * metainštrukcije in naredili neko tabelo oziroma prevajalnik program counterja + * v lokacijo v vektorju. problem je, da bi se morala prevajalska tabela vsakič znova + * regenerirati, ko spreminjamo program memory. + * */ + template class Program { + private: + vector storage; + public: + Ov * ov; + value_type & peek (index_type addr) { + return storage[addr]; + } + void poke (index_type addr, value_type val) { + storage[addr] = val; + } + Program (Ov *); + }; + class Ov { + private: + public: + queue inbuf; + queue outbuf; +#define IS2RAS(is) ((is*8-2)/2) + class ras { // IS + private: // IT + public: // REALLY + unsigned short int & is; // THIS + operator unsigned short int (void) { // HARD + return IS2RAS(is); // TO + } // DEFINE + ras (class Ov * ov) : is(ov->is) {} // A + }; // GETTER + ras ras{this}; // FUNCTION + class rs { + private: + public: + unsigned short int & is; + operator unsigned int (void) { + return 1 << IS2RAS(is); + } + rs (class Ov * ov) : is(ov->is) {} + }; + rs rs{this}; + class ps { + private: + public: + unsigned short int & pas; + operator unsigned int (void) { + return 1 << pas; + } + ps (class Ov * ov) : pas(ov->pas) {} + }; + ps ps{this}; + unsigned short int is; + unsigned short int pas; + bool io[IOLEN]; + bool opts[OPTSLEN]; + unsigned int pc = 0; /* where the program starts --- at zero or where? */ + Ram rstor{this}; + Mmu> ram{rstor}; + Program pstor{this}; + Mmu> pm{pstor}; + Ov (unsigned short int is = 2, unsigned short int pas = 16) + : is(is), pas(pas) { + for (int i = 0; i < OPTSLEN; i++) + opts[i] = 0; + for (int i = 0; i < IOLEN; i++) + io[i] = 0; + } + void step (void); + bool out (void); + char outc (void); + void in (bool); + void inc (char); + struct instr deserialize (const char *); + void deserialize (istream &, unsigned int); + void pd (ostream); // print debug + }; +} -- cgit v1.2.3