Let's keep the PC incremented to the actual value it's supposed to have in a real arm processor (current instruction +8).
...
...
@@ -7,8 +9,15 @@ struct ProcessorState
since in an actual execution the offset starts at 0 and moves to +8 after two instructions. decode() should probably account for this.
IMPLEMENTED
I just found out bit fields exist in C, so maybe we can use those to represent instructions (if it's more convenient). Or just pass them as a uint32_t.
Remember to check memory's bounds before accessing it!
Use uint32_t to represent data/instructions/register values
Use int to represent booleans and anything that doesn't need more than 16 bits (apparently ints can be only 16 bits on some systems)
int32_t applyOperation(int opcode, int32_t operand1, int32_t operand2, int *carry, int *store)
Applies a binary operation (from an opcode) on two operators. Returns the result, and sets the supplied carry bit if it makes sense to do so.
Doesn't change the carry bit when performing a logical operation.
void multiplyInstr(struct ProcessorState *processor, int address)
Executes a multiplication instruction stored in (*processor).memory[address]
...
...
@@ -30,33 +43,36 @@ void dataTransferInstr(struct ProcessorState *processor, int address)
void branchInstr(stuct ProcessorState *processor, int address)
uses: isValidCond(...),
struct ProcessorState *loadBinary(char* src)
creates a new ProcessorState and loads its memory from a binary file.
It's probably a good idea for this to check the edianness of the computer the emulator is running on (which is apparently possible at runtime) and flip the
binary data accordingly when loading it, as things would get really confusing otherwise (operations like arithmetic right shifts wouldn't work properly)
Helper functions (used in more than one method):
HELPER FUNCTIONS (probably used in more than one method):
int32_t getBits(int source, int start, int end)
uint32_t getBits(uint32_t source, int start, int end)
Reads bits from source in the range [start, end] (inclusive). Returns a uint32_t with the result as its least significant bits.
(e.g. getBits(8, 2, 3) == 2)
IMPLEMENTED
int isSet(int source, int index)
int isSet(uint32_t source, int index)
Shorthand for getBits(source, index, index)
IMPLEMENTED
int validCond(int cond, struct ProcessorState *processor)
int validCond(uint32_t cond, struct ProcessorState *processor)
Returns 1 if the condition code 'cond' is satisfied by the processor's current state, and 0 otherwise.
IMPLEMENTED
int32_t barrelShift(int immediate, int32_t operand, int *carry, struct ProcessorState *processor)
uint32_t barrelShift(int immediate, uint32_t operand, int *carry, struct ProcessorState *processor)
Implements the functionality of the barrel shifter. The carry output is written to (*carry), just in case you need it.
NOTE: for some reason the I bit is used in opposite ways for data processing instructions vs for data transfer instructions. Arbitrarily, let this method
assume that the data processing instruction interpretation is the right one (so for data transfer instructions just call this method with NOT i).
(optionally)
int32_t *accessMemory(struct ProcessorState *processor, int address)
Gets an address from memory while ensuring it's not out of bounds
(use if you want, you can always do bounds checking manually)
(only if you want; you can always do bounds checking manually)
// the spec says that the bottom byte of the register defines the shift amount... but this allows values much bigger than 31 (which is the max shift of all the other shift operation types)
// I'm not sure if it's a mistake or intentional - for now I'm just loading the bottom 5 bytes (2^5 = 32), since shifting an int32_t by more than 32 is undefined for C's bitwise operators.
// the phrasing in the spec is a little weird (I'm not sure what counts as a borrow for a subtraction, I'm pretty sure they mean an overflow)
// according to https://iitd-plos.github.io/col718/ref/arm-instructionset.pdf (top of pg. 12) "The C flag will be set to the carry out of bit 31 of the ALU"
// I'll just go with that for now
// TODO: figure out if I interpreted that right
switch(opcode)
{
// tst, and
case0:
returnoperand1&operand2;
// teq, eor
case1:
returnoperand1^operand2;
// or
case12:
returnoperand1|operand2;
// rsb, sub, add respectively (also cmp)
case3:
// rsb
int32_tswap=operand1;
operand1=operand2;
operand2=swap;
case2:
// sub
operand2=(~operand2)+1;
case4:
// add
inttopBit1=isSet(operand1,31);
inttopBit2=isSet(operand2,31);
uint32_tresult=operand1+operand2;
inttopBitRes=isSet(result,31);
// check for overflow:
// 1... + 1... is an overflow; so is 1... + 0... = 0... (a carry bit must have caused the top bit to become 0)