Skip to content
Snippets Groups Projects
Commit c5950fb9 authored by iftach's avatar iftach
Browse files

implemented applyOperation (untested)

parent 023792ac
No related branches found
No related tags found
No related merge requests found
Spec idea:
DATA STUFF
struct ProcessorState
Represents a processor; registers and memory.
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)
NON-HELPER FUNCTIONS
main
uses: loadBinary(...), step(...)
......@@ -18,7 +27,11 @@ void step(struct ProcessorState *processor)
void dataProcessingInstr(struct ProcessorState *processor, int address)
Executes a data processing instruction stored in (*processor).memory[address]
uses: isValidCond(...), barrelShift(...)
uses: isValidCond(...), barrelShift(...), applyOperation(...)
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)
......@@ -5,32 +5,38 @@
#define MEMORY_SIZE 65536
// Helpful stuff (things that will probably be used to implement more than one instruction type)
// apparently int can be as little as 16 bits, so I use ints only for boolean values and values that are always smaller than 16 bits,
// and int32_t / uint32_t to represent everything else.
// uint32_t is used for binary data instead of int32_t because it behaves more predictably (especially in shifts and conversions)
// struct representing a processor:
struct ProcessorState
{
int32_t registers[17];
int32_t memory[MEMORY_SIZE];
uint32_t registers[17];
uint32_t memory[MEMORY_SIZE];
};
// Reads bits from source in the range [start, end] (inclusive). Returns an int32_t with the result as its least significant bits.
// (e.g. getBits(8, 2, 3) == 2)
int32_t getBits(int source, int start, int end)
uint32_t getBits(uint32_t source, int start, int end)
{
return (source & ((2 << end) - (1 << start))) >> start;
}
// Shorthand for getBits(source, index, index)
int isSet(int source, int index)
int isSet(uint32_t source, int index)
{
return (source & (1 << index));
}
// Returns 1 if the condition code 'cond' is satisfied by the processor's current state, and 0 otherwise.
// Any condition code not listed in the spec will cause this to return 0.
int validCond(int32_t cond, struct ProcessorState *processor)
int validCond(uint32_t cond, struct ProcessorState *processor)
{
int32_t cprs = (*processor).registers[16];
// cprs register
uint32_t cprs = (*processor).registers[16];
// cprs flags
int n = isSet(cprs, 31);
int z = isSet(cprs, 30);
int c = isSet(cprs, 29);
......@@ -62,33 +68,35 @@ Implements the barrel shifter.
immediate: the I bit from the spec. Determines whether to shift by immediate value. Boolean type.
operand: the entire shift instruction (12 bits long). Operand2 on the spec.
carry: the carry-out bit (in case flags need to be updated).
carry: the carry-out bit (in case flags need to be updated). Boolean type.
processor: the processor (needed for reading from the registers).
this is completely untested and probably buggy, for all I know this becomes self-aware at runtime so don't trust anything it tells you
also, this doesn't cast return types; i don't think it's a problem but this is C so who knows what it's thinking
*/
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)
{
if (immediate)
{
//*** shift using immediate value ***//
unsigned int shiftCount = (unsigned int)getBits(operand, 8, 11);
uint32_t operandVal = (uint32_t)getBits(operand, 0, 7);
uint32_t operandVal = getBits(operand, 0, 7);
// shifting an int32_t by 32 bits is undefined behaviour in C, and this if statement prevents it.
if(shiftCount == 0){
// shifting an int32_t by 32 bits is undefined behaviour in C, and this if statement prevents it from happening later.
if (shiftCount == 0)
{
return operandVal;
}
// set operandVal to the result after rotation
operandVal = (operandVal >> (2 * shiftCount )) | (operandVal << (32 - 2 * shiftCount));
operandVal = (operandVal >> (2 * shiftCount)) | (operandVal << (32 - 2 * shiftCount));
// set c to the last bit of operandVal (the last bit to be rotated)
(*carry) = isSet(operandVal, 31);
return operandVal;
}
uint32_t operandVal = (uint32_t)(*processor).registers[getBits(operand, 0, 3)];
// this kind of register access is safe as 4 bits cannot reach above 17
uint32_t operandVal = (*processor).registers[getBits(operand, 0, 3)];
unsigned int shiftType = getBits(operand, 5, 6);
unsigned int shiftCount;
if (isSet(operand, 4))
......@@ -97,9 +105,7 @@ int32_t barrelShift(int immediate, int32_t operand, int *carry, struct Processor
uint32_t shiftReg = getBits(operand, 8, 11);
// register should not be the PC
assert((shiftReg != 15));
// 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.
shiftCount = getBits((*processor).registers[shiftReg], 0, 4);
shiftCount = getBits((*processor).registers[shiftReg], 0, 7);
}
else
{
......@@ -114,27 +120,122 @@ int32_t barrelShift(int immediate, int32_t operand, int *carry, struct Processor
}
//*** perform the shift ***//
// the behaviour for when shiftCount >= 32 (when shiftCount comes from a register) is a little ambiguous in the spec,
// so I got it from here: https://iitd-plos.github.io/col718/ref/arm-instructionset.pdf (bottom of page 14)
switch (shiftType)
{
// logical left shift
case 0:
// logical left shift
// this can happen if shiftCount comes from a register:
if (shiftCount >= 32)
{
carry = shiftCount == 32 ? isSet(operandVal, 0) : 0;
return 0;
}
(*carry) = isSet(operandVal, 32 - shiftCount);
return operandVal << shiftCount;
// logical right shift
case 1:
// logical right shift
// operandVal is a uint32_t, so C shouldn't sign-extend it
// this can happen if shiftCount comes from a register:
if (shiftCount >= 32)
{
carry = shiftCount == 32 ? isSet(operandVal, 31) : 0;
return 0;
}
(*carry) = isSet(operandVal, shiftCount - 1);
// operandVal is a uint32_t, so C shouldn't sign-extend it
return operandVal >> shiftCount;
// arithmetic right shift
case 2:
// arithmetic right shift
// this can happen if shiftCount comes from a register:
if (shiftCount >= 32)
{
uint32_t result = 0;
(*carry) = isSet(operandVal, 31);
if ((*carry))
{
result = ~result;
}
return result;
}
(*carry) = isSet(operandVal, shiftCount - 1);
// convert operandVal to a signed type, then shift it
return (int32_t)operandVal >> shiftCount;
// rotate right
case 3:
// rotate right
// this can happen if shiftCount comes from a register:
if (shiftCount == 32)
{
(*carry) = isSet(operandVal, 31);
return operandVal;
}
shiftCount = shiftCount % 32;
(*carry) = isSet(operand, shiftCount - 1);
return (operandVal >> shiftCount) | (operandVal << (32 - shiftCount));
}
};
}
// TODO: applyOperation(int opcode, int32_t operator1, int32_t operator2, int *carry)
/*
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.
Does not change the carry bit when performing a logical operation.
opcode: the opcode
operand1: The content of register Rn on the spec
operand2: The value of Operand2 on the spec after shifting
carry: Carry bit output (boolean)
store: A bit representing whether the result should be stored (some instructions like TST discard the result) (boolean)
*/
int32_t applyOperation(int opcode, int32_t operand1, int32_t operand2, int *carry, int *store)
{
(*store) = (opcode != 8) && (opcode != 9) && (opcode != 10);
// 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
case 0:
return operand1 & operand2;
// teq, eor
case 1:
return operand1 ^ operand2;
// or
case 12:
return operand1 | operand2;
// rsb, sub, add respectively (also cmp)
case 3:
// rsb
int32_t swap = operand1;
operand1 = operand2;
operand2 = swap;
case 2:
// sub
operand2 = (~operand2) + 1;
case 4:
// add
int topBit1 = isSet(operand1, 31);
int topBit2 = isSet(operand2, 31);
uint32_t result = operand1 + operand2;
int topBitRes = 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)
(*carry) = (topBit1 && topBit2) || ((topBit1 || topBit2) && !topBitRes);
return result;
case 13:
return operand2;
};
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment