Newer
Older
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#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
{
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.
uint32_t getBits(uint32_t source, int start, int end)
{
return (source & ((2 << end) - (1 << start))) >> start;
}
// Shorthand for getBits(source, index, 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(uint32_t cond, struct ProcessorState *processor)
// 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);
int v = isSet(cprs, 28);
switch (cond)
{
case 0:
case 10:
return n == z;
case 11:
return n != z;
case 12:
return !z && (n == v);
case 13:
return z || (n != v);
case 14:
return 1;
default:
return 0;
}
}
/*
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). 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
*/
uint32_t barrelShift(int immediate, uint32_t operand, int *carry, struct ProcessorState *processor)
//*** shift using immediate value ***//
unsigned int shiftCount = (unsigned int)getBits(operand, 8, 11);
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 from happening later.
if (shiftCount == 0)
{
return operandVal;
}
// set operandVal to the result after rotation
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;
// 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))
{
//*** shift using shift count stored in a register ***//
uint32_t shiftReg = getBits(operand, 8, 11);
// register should not be the PC
assert((shiftReg != 15));
shiftCount = getBits((*processor).registers[shiftReg], 0, 7);
}
else
{
//*** shift using immediate shift count ***//
shiftCount = getbits(operand, 7, 11);
}
// Exit early if not shifting. This makes some of the upcoming logic simpler.
if (shiftCount == 0)
{
return operandVal;
}
// 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)
// 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;
// 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
// 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;
// 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));
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
/*
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;
};
}