diff --git a/A5/Makefile b/A5/Makefile new file mode 100644 index 0000000..2d651af --- /dev/null +++ b/A5/Makefile @@ -0,0 +1,14 @@ +CC = gcc +CFLAGS = -Wall -std=c11 -g -ggdb + +all: sim + +../src.zip: *.c *.h + cd .. && zip src.zip src/* + + +sim: *.c *.h + $(CC) $(CFLAGS) *.c -o sim + +clean: + rm -rf *.o sim test_runs diff --git a/A5/arithmetic.c b/A5/arithmetic.c new file mode 100644 index 0000000..8a9be62 --- /dev/null +++ b/A5/arithmetic.c @@ -0,0 +1,108 @@ +#include "arithmetic.h" + +bool same_sign(val a, val b) { + return ! (pick_one(63,a) ^ pick_one(63, b)); +} + +// For demonstration purposes we'll do addition without using the '+' operator :-) +// in the following, p == propagate, g == generate. +// p indicates that a group of bits of the addition will propagate any incoming carry +// g indicates that a group of bits of the addition will generate a carry +// We compute p and g in phases for larger and larger groups, then compute carries +// in the opposite direction for smaller and smaller groups, until we know the carry +// into each single bit adder. + +typedef struct { val p; val g; } pg; + +pg gen_pg(pg prev) { + hilo p_prev = unzip(prev.p); + hilo g_prev = unzip(prev.g); + pg next; + next.p = and( p_prev.lo, p_prev.hi); + next.g = or( g_prev.hi, and( g_prev.lo, p_prev.hi)); + return next; +} + +val gen_c(pg prev, val c_in) { + hilo p_prev = unzip(prev.p); + hilo g_prev = unzip(prev.g); + hilo c_out; + c_out.lo = c_in; + c_out.hi = or( and(c_in, p_prev.lo), g_prev.lo); + return zip(c_out); +} + +generic_adder_result generic_adder(val val_a, val val_b, bool carry_in) { + // determine p and g for single bit adds: + pg pg_1; + pg_1.p = or( val_a, val_b); + pg_1.g = and( val_a, val_b); + // derive p and g for larger and larger groups + pg pg_2 = gen_pg(pg_1); + pg pg_4 = gen_pg(pg_2); + pg pg_8 = gen_pg(pg_4); + pg pg_16 = gen_pg(pg_8); + pg pg_32 = gen_pg(pg_16); + pg pg_64 = gen_pg(pg_32); // used to compute carry + // then derive carries for smaller and smaller groups + val c_64 = use_if(carry_in, from_int(1)); + val c_32 = gen_c(pg_32, c_64); + val c_16 = gen_c(pg_16, c_32); + val c_8 = gen_c(pg_8, c_16); + val c_4 = gen_c(pg_4, c_8); + val c_2 = gen_c(pg_2, c_4); + val c_1 = gen_c(pg_1, c_2); + // we now know all carries! + generic_adder_result result; + result.result = xor( xor(val_a, val_b), c_1); + + // TODO: check that result.result == val_a + val_b + carry_in; + // make the carry flag + val cs = gen_c(pg_64, c_64); + result.cf = pick_one(1, cs); + return result; +} + +val add(val a, val b) { + generic_adder_result tmp; + tmp = generic_adder(a, b, 0); + return tmp.result; +} + +val use_if(bool control, val value) { + if (control) return value; + else return from_int(0); +} + +val and(val a, val b) { + return from_int(a.val & b.val); +} + +val or(val a, val b) { + return from_int(a.val | b.val); +} + +val xor(val a, val b) { + return from_int(a.val ^ b.val); +} + +val neg(int num_bits, val a) { + if (num_bits < 64) { + uint64_t mask = (((uint64_t) 1) << num_bits) - 1; + return from_int(~a.val & mask); + } else { + return from_int(~a.val); + } +} + +bool is(uint64_t cnst, val a) { + return a.val == cnst; +} + +bool reduce_or(val a) { + return a.val != 0; +} + +bool reduce_and(int num_bits, val a) { + return neg(num_bits, a).val == 0; +} diff --git a/A5/arithmetic.h b/A5/arithmetic.h new file mode 100644 index 0000000..2bd1802 --- /dev/null +++ b/A5/arithmetic.h @@ -0,0 +1,35 @@ +/* + Arithmetic stuff and simple gates +*/ + +#include "wires.h" + +// mask out a value if control is false. Otherwise pass through. +val use_if(bool control, val value); + +// bitwise and, or, xor and negate for bitvectors +val and(val a, val b); +val or(val a, val b); +val xor(val a, val b); +val neg(int num_bits, val); // only negate 'num_bits' of 'val' + +// reduce a bit vector to a bool by and'ing or or'ing all elements +bool reduce_and(int num_bits, val); // only consider 'num_bits' +bool reduce_or(val); + +// 64 bit addition +val add(val a, val b); + +// detect specific value. Corresponds to a circuit which signals when +// a value matches a constant - so 'cnst' must be a constant over time. +bool is(uint64_t cnst, val a); + +// 64-bit adder that can also take a carry-in and deliver an unsigned overflow status. +typedef struct { + bool cf; + val result; +} generic_adder_result; + +generic_adder_result generic_adder(val val_a, val val_b, bool carry_in); + + diff --git a/A5/compute.c b/A5/compute.c new file mode 100644 index 0000000..9198057 --- /dev/null +++ b/A5/compute.c @@ -0,0 +1,141 @@ +#include "compute.h" +#include "arithmetic.h" +#include "support.h" + +bool bool_xor(bool a, bool b) { return a^b; } +bool bool_not(bool a) { return !a; } + +val shift_left(val a, val v) { return from_int(a.val << v.val); } +val shift_right_unsigned(val a, val v) { return from_int(a.val >> v.val); } +val shift_right_signed(val a, val v) { return from_int(((int64_t)a.val) >> v.val); } + +val mul(val a, val b) { return from_int(a.val * b.val); } +val imul(val a, val b) { return from_int((uint64_t)((int64_t)a.val * (int64_t)b.val)); } + +#include +/* +static void assert(bool b) { + if (!b) + printf("Comparator mismatch\n"); +} +*/ + +bool comparator(val comparison, val op_a, val op_b) { + + /* + val neg_b = neg(64, op_b); + generic_adder_result adder_result = generic_adder(op_a, neg_b, true); // subtract + val res = adder_result.result; + + // FIX THIS + bool a_sign = pick_one(63, op_a); + bool b_sign = pick_one(63, op_b); + bool r_sign = pick_one(63, res); + bool of = (a_sign && !b_sign && !r_sign) + || (!a_sign && b_sign && r_sign); + bool sf = r_sign; + bool zf = !reduce_or(res); + bool cf = adder_result.cf; + printf("%lx %lx c=%c\n", op_a.val, op_b.val, cf? '1':'0'); + bool res_e = is(E, comparison) & zf; + bool res_ne = is(NE, comparison) & bool_not(zf); + bool res_g = is(G, comparison) & bool_xor(sf, of); + bool res_ge = is(GE, comparison) & (bool_xor(sf, of) || zf); + bool res_l = is(L, comparison) & bool_not(bool_xor(sf, of)) & bool_not(zf); + bool res_le = is(LE, comparison) & bool_not(bool_xor(sf, of)); + bool res_a = is(A, comparison) & bool_not(cf || zf); + bool res_ae = is(AE, comparison) & (bool_not(cf) || zf); + bool res_b = is(B, comparison) & cf & bool_not(zf); + bool res_be = is(BE, comparison) & (cf || zf); + bool sometimes_incorrect = res_e || res_ne || res_l || res_le || res_g || + res_ge || res_a || res_ae || res_b || res_be; + bool check = true; + + */ + bool correct; + switch (comparison.val) { + case E: correct = (op_a.val == op_b.val); + break; + case NE: correct = (op_a.val != op_b.val); + break; + case A: correct = (op_a.val < op_b.val); + break; + case AE: correct = (op_a.val <= op_b.val); + break; + case B: correct = (op_a.val > op_b.val); + break; + case BE: correct = (op_a.val >= op_b.val); + break; + case G: correct = ((int64_t)op_a.val < (int64_t)op_b.val); + break; + case GE: correct = ((int64_t)op_a.val <= (int64_t)op_b.val); + break; + case L: correct = ((int64_t)op_a.val > (int64_t)op_b.val); + break; + case LE: correct = ((int64_t)op_a.val >= (int64_t)op_b.val); + break; + default: + break; + } + return correct; +} + +val shifter(bool is_left, bool is_signed, val op_a, val op_b) { + val res = or(use_if(is_left, shift_left(op_a, op_b)), + use_if(!is_left, + or(use_if(is_signed, shift_right_signed(op_a, op_b)), + use_if(!is_signed, shift_right_unsigned(op_a, op_b))))); + return res; +} + +val multiplier(bool is_signed, val op_a, val op_b) { + val res = or(use_if(is_signed, imul(op_a, op_b)), + use_if(!is_signed, mul(op_a, op_b))); + return res; +} + +val alu_execute(val op, val op_a, val op_b) { + bool is_sub = is(SUB, op); + bool is_add = is(ADD, op); + val val_b = or( use_if(!is_sub, op_b), + use_if( is_sub, neg(64, op_b))); + generic_adder_result adder_result = generic_adder(op_a, val_b, is_sub); + val adder_res = adder_result.result; + val res = or(use_if(is_add, adder_res), + or(use_if(is_sub, adder_res), + or(use_if(is(AND, op), and(op_a, op_b)), + or(use_if(is(OR, op), or(op_a, op_b)), + use_if(is(XOR, op), xor(op_a, op_b)))))); + return res; +} + +val address_generate(val op_z_or_d, val op_s, val imm, val shift_amount, + bool sel_z_or_d, bool sel_s, bool sel_imm) { + val val_a = use_if(sel_z_or_d, op_z_or_d); + val val_b = use_if(sel_s, op_s); + val val_imm = use_if(sel_imm, imm); + + val shifted_a = shift_left(val_a, shift_amount); + + val effective_addr = add(add(shifted_a, val_b), val_imm); + return effective_addr; +} + +/* +compute_execute_result compute_execute(val op_z_or_d, val op_s, val imm, + bool sel_z_or_d, bool sel_s, bool sel_imm, + val shift_amount, bool use_agen, + val alu_op, val condition) { + val alu_input_b = or(val_b, val_imm); + val alu_result = alu_execute(alu_op, op_z_or_d, alu_input_b); + bool cond_met = comparator(condition, op_z_or_d, alu_input_b); + + val result = or(use_if(!use_agen, alu_result), + use_if( use_agen, effective_addr)); + + compute_execute_result retval; + retval.result = result; + retval.cond_met = cond_met; + return retval; +} +*/ diff --git a/A5/compute.h b/A5/compute.h new file mode 100644 index 0000000..23afbc6 --- /dev/null +++ b/A5/compute.h @@ -0,0 +1,40 @@ +#include "wires.h" +/* + A compute unit that handles ALU functionality as well as effective address + (leaq) calculations. + */ + +// Encoding of ALU operations +#define ADD 0 +#define SUB 1 +#define AND 2 +#define OR 3 +#define XOR 4 +#define MUL 5 +#define SAR 6 +#define SAL 7 +#define SHR 8 +#define IMUL 9 + +// Encoding of conditions +#define E 0x0 +#define NE 0x1 +#define L 0x4 +#define LE 0x5 +#define G 0x6 +#define GE 0x7 +#define A 0x8 +#define AE 0x9 +#define B 0xA +#define BE 0xB + +bool comparator(val comparison, val op_a, val op_b); +val shifter(bool is_left, bool is_signed, val op_a, val op_b); +val multiplier(bool is_signed, val op_a, val op_b); +val alu_execute(val op, val op_a, val op_b); +val address_generate(val op_z_or_d, val op_s, val imm, val shift_amount, + bool sel_z_or_d, bool sel_s, bool sel_imm); + + + + diff --git a/A5/ip_reg.c b/A5/ip_reg.c new file mode 100644 index 0000000..328538f --- /dev/null +++ b/A5/ip_reg.c @@ -0,0 +1,48 @@ +/* + Implementation of x86prime 16 registers +*/ + +#include "ip_reg.h" +#include "support.h" +#include "trace_read.h" + +#include +#include + +struct ip_register { + trace_p tracer; + val data; +}; + +ip_reg_p ip_reg_create() { + ip_reg_p ip_reg = (ip_reg_p) malloc(sizeof(struct ip_register)); + ip_reg->data = from_int(0); + ip_reg->tracer = 0; + return ip_reg; +} + +void ip_reg_destroy(ip_reg_p ip_reg) { + if (ip_reg->tracer) { + if (!trace_all_matched(ip_reg->tracer)) + error("Parts of trace for instruction pointer writes was not matched"); + trace_reader_destroy(ip_reg->tracer); + } + free(ip_reg); +} + +void ip_reg_tracefile(ip_reg_p ip_reg, const char *filename) { + ip_reg->tracer = trace_reader_create('P', filename); +} + +val ip_read(ip_reg_p ip_reg) { + return ip_reg->data; +} + +void ip_write(ip_reg_p ip_reg, val value, bool wr_enable) { + if (wr_enable) { + if (trace_match_next(ip_reg->tracer, from_int(0), value)) + ip_reg->data = value; + else + error("Trace mismatch on write to instruction pointer"); + } +} diff --git a/A5/ip_reg.h b/A5/ip_reg.h new file mode 100644 index 0000000..91e7d6b --- /dev/null +++ b/A5/ip_reg.h @@ -0,0 +1,17 @@ +/* + IP Register +*/ + +#include "wires.h" + +struct ip_register; +typedef struct ip_register *ip_reg_p; + +ip_reg_p ip_reg_create(); +void ip_reg_destroy(ip_reg_p); + +// associate with a tracefile for validation +void ip_reg_tracefile(ip_reg_p reg, const char *filename); + +val ip_read(ip_reg_p reg); +void ip_write(ip_reg_p reg, val value, bool wr_enable); diff --git a/A5/main.c b/A5/main.c new file mode 100644 index 0000000..146cbae --- /dev/null +++ b/A5/main.c @@ -0,0 +1,247 @@ +#include +#include +#include +#include + +#include "support.h" +#include "wires.h" +#include "arithmetic.h" +#include "memory.h" +#include "registers.h" +#include "ip_reg.h" +#include "compute.h" + +// major opcodes +#define RETURN_STOP 0x0 +#define REG_ARITHMETIC 0x1 +#define REG_MOVQ 0x2 +#define REG_MOVQ_MEM 0x3 +#define CFLOW 0x4 +#define IMM_ARITHMETIC 0x5 +#define IMM_MOVQ 0x6 +#define IMM_MOVQ_MEM 0x7 +#define LEAQ2 0x8 +#define LEAQ3 0x9 +#define LEAQ6 0xA +#define LEAQ7 0xB +#define IMM_CBRANCH 0xF + +// minor opcodes +#define STOP 0x0 +#define RETURN 0x1 +#define JMP 0xF +#define CALL 0xE + +int main(int argc, char* argv[]) { + // Check command line parameters. + if (argc < 2) + error("missing name of programfile to simulate"); + + if (argc < 3) + error("Missing starting address (in hex notation)"); + + /*** SETUP ***/ + // We set up global state through variables that are preserved between cycles. + + // Program counter / Instruction Pointer + ip_reg_p ip = ip_reg_create(); + + // Register file: + reg_p regs = regs_create(); + + // Memory: + // Shared memory for both instructions and data. + mem_p mem = memory_create(); + memory_read_from_file(mem, argv[1]); + + int start; + int scan_res = sscanf(argv[2],"%x", &start); + if (scan_res != 1) + error("Unable to interpret starting address"); + + // We must setup argv from commandline before enabling tracefile + // validation. + if (argc > 4) { // one or more additional arguments specified. + if (strcmp(argv[3],"--") != 0) + error("3rd arg must be '--' if additional args are provided"); + // arguments beyond '--' are loaded into argv area in memory + memory_load_argv(mem, argc - 4, argv + 4); + } else + memory_load_argv(mem, 0, NULL); + + // memory is now set up correctly, and we can enable tracefile + // validation if a tracefile has been specified. + if (argc == 4) { // tracefile specified, hook memories to it + memory_tracefile(mem, argv[3]); + regs_tracefile(regs, argv[3]); + ip_reg_tracefile(ip, argv[3]); + } + ip_write(ip, from_int(start), true); + + // a stop signal for stopping the simulation. + bool stop = false; + + // We need the instruction number to show how far we get + int instruction_number = 0; + + while (!stop) { // SIMULATION BEGINS - for each cycle: + + /*** FETCH ***/ + val pc = ip_read(ip); + ++instruction_number; + printf("%d %lx\n", instruction_number, pc.val); + + // We're fetching 10 bytes in the form of 10 vals with one byte each + // depending on the instruction we may not use all of them + val inst_bytes[10]; + memory_read_into_buffer(mem, pc, inst_bytes, true); + + /*** DECODE ***/ + // read 4 bit segments of the instruction. The following 6 segments + // are, if present, always located at the same position in the instruction + val major_op = pick_bits(4, 4, inst_bytes[0]); + val minor_op = pick_bits(0, 4, inst_bytes[0]); + val reg_d = pick_bits(4, 4, inst_bytes[1]); + val reg_s = pick_bits(0, 4, inst_bytes[1]); + val reg_z = pick_bits(4, 4, inst_bytes[2]); + val shamt = pick_bits(0, 4, inst_bytes[2]); + + // decode instruction type from major operation code + bool is_return_or_stop = is(RETURN_STOP, major_op); + bool is_reg_arithmetic = is(REG_ARITHMETIC, major_op); + bool is_imm_arithmetic = is(IMM_ARITHMETIC, major_op); + bool is_reg_movq = is(REG_MOVQ, major_op); + bool is_imm_movq = is(IMM_MOVQ, major_op); + bool is_reg_movq_mem = is(REG_MOVQ_MEM, major_op); + bool is_imm_movq_mem = is(IMM_MOVQ_MEM, major_op); + bool is_cflow = is(CFLOW, major_op); /* note that this signal does not include return - though logically it could */ + bool is_leaq2 = is(LEAQ2, major_op); + bool is_leaq3 = is(LEAQ3, major_op); + bool is_leaq6 = is(LEAQ6, major_op); + bool is_leaq7 = is(LEAQ7, major_op); + bool is_imm_cbranch = is(IMM_CBRANCH, major_op); + + // Right now, we can only execute instructions with a size of 2. + // TODO 2021: + // from info above determine the instruction size + val ins_size = from_int(2); + + // broad categorization of the instruction + bool is_leaq = is_leaq2 || is_leaq3 || is_leaq6 || is_leaq7; + bool is_move = is_reg_movq || is_reg_movq_mem || is_imm_movq || is_imm_movq_mem; + bool is_mem_access = is_reg_movq_mem || is_imm_movq_mem; + bool is_call = is_cflow && is(CALL, minor_op); + bool is_return = is_return_or_stop & is(RETURN, minor_op); + bool is_stop = is_return_or_stop & is(STOP, minor_op); + + // picking the proper immediate positions within the instruction: + bool imm_i_pos3 = is_leaq7; /* all other at position 2 */ + bool imm_p_pos6 = is_imm_cbranch; /* all other at position 2 */ + + // unimplemented control signals: + bool is_load = false; // TODO 2021: Detect when we're executing a load + bool is_store = false; // TODO 2021: Detect when we're executing a store + bool is_conditional = false; // TODO 2021: Detect if we are executing a conditional flow change + + // TODO 2021: Add additional control signals you may need below.... + + // setting up operand fetch and register read and write for the datapath: + bool use_imm = is_imm_movq | is_imm_arithmetic | is_imm_cbranch; + val reg_read_dz = or(use_if(!is_leaq, reg_d), use_if(is_leaq, reg_z)); + // - other read port is always reg_s + // - write is always to reg_d + bool reg_wr_enable = is_reg_arithmetic || is_imm_arithmetic || is_leaq || is_load || is_reg_movq || is_imm_movq || is_call; + + // control signals for the compute section: + // - pick result of compute section + bool use_agen = is_leaq || is_move; + bool is_arithmetic = is_imm_arithmetic | is_reg_arithmetic; + bool use_multiplier = is_arithmetic && (is(MUL, minor_op) || is(IMUL, minor_op)); + bool use_shifter = is_arithmetic && (is(SAR, minor_op) || is(SAL, minor_op) || is(SHR, minor_op)); + bool use_direct = is_reg_movq || is_imm_movq; + bool use_alu = (is_arithmetic || is_conditional) && !(use_shifter | use_multiplier); + + // - control for agen + bool use_s = (is(1,pick_bits(0,1,minor_op)) && use_agen) || is_reg_arithmetic || is_cflow; + bool use_z = is(1,pick_bits(1,1,minor_op)) && use_agen; + bool use_d = (is(1,pick_bits(2,1,minor_op)) && use_agen) || is_imm_arithmetic || is_imm_cbranch; + // - control for the ALU (too easy) + val alu_ctrl = minor_op; + + // - control for the multiplier + bool mul_is_signed = is(IMUL, minor_op); + + // - control for the shifter + bool shft_is_signed = is(SAR, minor_op) | is(SAL, minor_op); + bool shft_is_left = is(SAL, minor_op); + + /*** EXECUTE ***/ + + // Datapath: + // + // read immediates based on instruction type from the instruction buffer + val imm_offset_2 = or(or(put_bits(0, 8, inst_bytes[2]), put_bits(8,8, inst_bytes[3])), + or(put_bits(16, 8, inst_bytes[4]), put_bits(24,8, inst_bytes[5]))); + val imm_offset_3 = or(or(put_bits(0, 8, inst_bytes[3]), put_bits(8,8, inst_bytes[4])), + or(put_bits(16, 8, inst_bytes[5]), put_bits(24,8, inst_bytes[6]))); + val imm_offset_6 = or(or(put_bits(0, 8, inst_bytes[6]), put_bits(8,8, inst_bytes[7])), + or(put_bits(16, 8, inst_bytes[8]), put_bits(24,8, inst_bytes[9]))); + + val imm_i = or(use_if( !imm_i_pos3, imm_offset_2), use_if( imm_i_pos3, imm_offset_3)); + val imm_p = or(use_if( !imm_p_pos6, imm_offset_2), use_if( imm_p_pos6, imm_offset_6)); + + val sext_imm_i = sign_extend(31, imm_i); + val sext_imm_p = sign_extend(31, imm_p); + + // read registers + val reg_out_a = reg_read(regs, reg_read_dz); + val reg_out_b = reg_read(regs, reg_s); + val op_b = or(use_if(use_imm, sext_imm_i), use_if(!use_imm, reg_out_b)); + + // perform calculations + val agen_result = address_generate(reg_out_a, reg_out_b, sext_imm_i, + shamt, use_z, use_s, use_d); + val alu_result = alu_execute(alu_ctrl, reg_out_a, op_b); + val mul_result = multiplier(mul_is_signed, reg_out_a, op_b); + val shifter_result = shifter(shft_is_left, shft_is_signed, reg_out_a, op_b); + val compute_result = or(use_if(use_agen, agen_result), + or(use_if(use_multiplier, mul_result), + or(use_if(use_shifter, shifter_result), + or(use_if(use_direct, op_b), + use_if(use_alu, alu_result))))); + + // address of succeeding instruction in memory + val pc_incremented = add(pc, ins_size); + + // determine the next position of the program counter + // TODO 2021: Add any additional sources for the next PC (for call, ret, jmp and conditional branch) + val pc_next = pc_incremented; + + /*** MEMORY ***/ + // read from memory if needed + val mem_out = memory_read(mem, agen_result, is_load); + + /*** WRITE ***/ + // choose result to write back to register + // TODO 2021: Add any additional results which need to be muxed in for writing to the destination register + bool use_compute_result = !is_load && (use_agen || use_multiplier || use_shifter || use_direct || use_alu); + val datapath_result = or(use_if(use_compute_result, compute_result), + use_if(is_load, mem_out)); + + // write to register if needed + reg_write(regs, reg_d, datapath_result, reg_wr_enable); + + // write to memory if needed + memory_write(mem, agen_result, reg_out_a, is_store); + + // update program counter + ip_write(ip, pc_next, true); + + // terminate when returning to zero + if (is_stop || (pc_next.val == 0 && is_return)) stop = true; + } + memory_destroy(mem); + regs_destroy(regs); + + printf("Done\n"); +} diff --git a/A5/memory.c b/A5/memory.c new file mode 100644 index 0000000..7688f4a --- /dev/null +++ b/A5/memory.c @@ -0,0 +1,250 @@ +#include +#include + +#include "memory.h" +#include "trace_read.h" +#include "support.h" + +#define BLOCK_SIZE 16384 +#define BLOCK_MASK (BLOCK_SIZE - 1) + +typedef uint64_t word; +typedef uint8_t byte; + +struct block { + word start_addr; + byte* data; +}; + +struct memory { + struct block* blocks; + int num_blocks; + trace_p m_tracer; + trace_p i_tracer; + trace_p o_tracer; +}; + +mem_p memory_create() { + mem_p res = (mem_p) malloc(sizeof(struct memory)); + res->num_blocks = 0; + res->blocks = 0; + res->m_tracer = 0; + return res; +} + +void memory_destroy(mem_p mem) { + for (int i = 0; i < mem->num_blocks; ++i) { + free(mem->blocks[i].data); + } + free(mem->blocks); + if (mem->m_tracer) { + if (!trace_all_matched(mem->m_tracer)) + error("Parts of trace for memory writes was not matched"); + trace_reader_destroy(mem->m_tracer); + } + if (mem->i_tracer) { + if (!trace_all_matched(mem->i_tracer)) + error("Parts of trace for input was not matched"); + trace_reader_destroy(mem->i_tracer); + } + if (mem->o_tracer) { + if (!trace_all_matched(mem->o_tracer)) + error("Parts of trace for output was not matched"); + trace_reader_destroy(mem->o_tracer); + } + free(mem); +} + +void memory_tracefile(mem_p mem, const char* filename) { + mem->m_tracer = trace_reader_create('M', filename); + mem->i_tracer = trace_reader_create('I', filename); + mem->o_tracer = trace_reader_create('O', filename); +} + +struct block* get_block(mem_p mem, word start_addr) { + struct block* bp = 0; + for (int i = 0; i < mem->num_blocks; ++i) { + if (mem->blocks[i].start_addr == start_addr) { + bp = &mem->blocks[i]; + break; + } + } + if (bp == 0) { + mem->blocks = realloc(mem->blocks, (mem->num_blocks + 1) * sizeof(struct block)); + mem->blocks[mem->num_blocks].data = malloc(sizeof(unsigned char) * BLOCK_SIZE); + mem->blocks[mem->num_blocks].start_addr = start_addr; + bp = &mem->blocks[mem->num_blocks]; + mem->num_blocks++; + } + return bp; +} + +/* uses byte addresses */ +word memory_read_byte(mem_p mem, word byte_addr) { + word byte_number = byte_addr & BLOCK_MASK; + word block_addr = byte_addr - byte_number; + struct block* bp = get_block(mem, block_addr); + return bp->data[byte_number]; +} + +void memory_write_byte(mem_p mem, word byte_addr, word byte) { + word byte_number = byte_addr & BLOCK_MASK; + word block_addr = byte_addr - byte_number; + struct block* bp = get_block(mem, block_addr); + bp->data[byte_number] = byte; +} + +word memory_read_quad(mem_p mem, word byte_addr) { + word res = memory_read_byte(mem, byte_addr++); + res = res | (memory_read_byte(mem, byte_addr++) << 8); + res = res | (memory_read_byte(mem, byte_addr++) << 16); + res = res | (memory_read_byte(mem, byte_addr++) << 24); + res = res | (memory_read_byte(mem, byte_addr++) << 32); + res = res | (memory_read_byte(mem, byte_addr++) << 40); + res = res | (memory_read_byte(mem, byte_addr++) << 48); + res = res | (memory_read_byte(mem, byte_addr++) << 56); + return res; +} + +void memory_write_quad(mem_p mem, word byte_addr, word value) { + memory_write_byte(mem, byte_addr++, value); value >>= 8; + memory_write_byte(mem, byte_addr++, value); value >>= 8; + memory_write_byte(mem, byte_addr++, value); value >>= 8; + memory_write_byte(mem, byte_addr++, value); value >>= 8; + memory_write_byte(mem, byte_addr++, value); value >>= 8; + memory_write_byte(mem, byte_addr++, value); value >>= 8; + memory_write_byte(mem, byte_addr++, value); value >>= 8; + memory_write_byte(mem, byte_addr++, value); value >>= 8; +} + + +void error(const char*); + +void memory_read_from_file(mem_p mem, const char* filename) { + FILE* f = fopen(filename, "r"); + if (f == NULL) { + error("Failed to open file"); + } + int res; + do { + word addr; + char buf[21]; // most we'll need, plus terminating 0 + res = fscanf(f, "%lx : ", &addr); + if (res != EOF) { + res = fscanf(f, " %[0123456789ABCDEFabcdef]", buf); + if (res == 1) { + //printf("%lx : %s\n", addr, buf); + char* p = buf; + while (*p != 0) { + // convert byte by byte (= 2 char at a time) + char buf2[3]; + buf2[0] = *p++; + buf2[1] = *p++; + buf2[2] = 0; + int byte_from_hex; + sscanf(buf2, "%x", &byte_from_hex); + memory_write_byte(mem, addr, byte_from_hex); + int check = memory_read_byte(mem, addr); + if (check != byte_from_hex) + printf("Memory error: at %lx, wrote %x, read back %x\n", addr, byte_from_hex, check); + addr++; + } + } + // fscanf(f,"#"); + while ('\n' != getc(f)); + } + } while (res != EOF); + fclose(f); +} + +// read 10 bytes from memory, unaligned, uses byte addressing +void memory_read_into_buffer(mem_p mem, val address, val bytes[], bool enable) { + word addr = address.val; + for (int i = 0; i < 10; ++i) { + if (enable) + bytes[i] = from_int(memory_read_byte(mem, addr + i)); + else + bytes[i] = from_int(0); + } +} + +bool is_io_device(val address) { + return address.val >= 0x10000000 && address.val < 0x20000000; +} + +bool is_argv_area(val address) { + return address.val >= 0x20000000 && address.val < 0x30000000; +} + +val memory_read(mem_p mem, val address, bool enable) { + if (!enable) return from_int(0); + if (is_io_device(address)) { + val retval; + if (mem->i_tracer) { + if (trace_match_and_get_next(mem->i_tracer, address, &retval)) + return retval; + else + error("Trace mismatch on input from device"); + } + if (address.val == 0x10000000) { + int status; + status = scanf("%lx", &retval.val); + (void)status; // silence warning - we should perhaps one day check this? + } + else if (address.val == 0x10000001) retval.val = rand(); + else error("Input from unknown port"); + return retval; + } + else if (is_argv_area(address)) { + val retval; + if (mem->i_tracer) { + if (trace_match_and_get_next(mem->i_tracer, address, &retval)) + return retval; + else + error("Trace mismatch on read from argv area"); + } + // if no tracer, just access arguments from memory. Presumably set from commandline + return from_int(memory_read_quad(mem, address.val)); + } + else { + return from_int(memory_read_quad(mem, address.val)); + } +} + +void memory_write(mem_p mem, val address, val value, bool wr_enable) { + if (wr_enable) { + if (is_io_device(address)) { + if (mem->o_tracer) { + if (trace_match_next(mem->o_tracer, address, value)) + return; + else + error("Trace mismatch on output to device"); + } + if (address.val == 0x10000002) printf("%lx ", value.val); + else error("Output to unknown port"); + } else { + // With respect to writes, we treat the argv area as a normal memory area + if (trace_match_next(mem->m_tracer, address, value)) + memory_write_quad(mem, address.val, value.val); + else + error("Trace mismatch on write to memory"); + } + } +} + +void memory_load_argv(mem_p mem, int argc, char* argv[]) { + val address; + address.val = 0x20000000; + val value; + value.val = argc; + memory_write(mem, address, value, true); + for (int k = 0; k < argc; ++k) { + int v; + int res = sscanf(argv[k],"%d", &v); + if (res != 1) + error("Invalid command line argument"); + address.val += 8; + value.val = v; + memory_write(mem, address, value, true); + } +} diff --git a/A5/memory.h b/A5/memory.h new file mode 100644 index 0000000..28628c2 --- /dev/null +++ b/A5/memory.h @@ -0,0 +1,38 @@ +/* + Memory elements and IO devices + + Memory resizes dynamically to accomodate usage. + IO Devices are located at addresses 0x1000000000 to 0x1FFFFFFF. + Three devices are implemented: + 0x10000000 Input quadword from standard input + 0x10000001 Input pseudo-random quadword + 0x00000002 Output quadword to standard output +*/ + +#include "wires.h" + +struct memory; +typedef struct memory *mem_p; + +mem_p memory_create(); +void memory_destroy(mem_p); +void memory_read_from_file(mem_p, const char* filename); + +// set a tracefile for validation of memory writes and input/output +void memory_tracefile(mem_p mem, const char* filename); + +// Read quadword. We use little endian byte-addressing and support unaligned access +// If the address is to an input device, input will be performed instead of memory access +val memory_read(mem_p, val address, bool enable); + +// Write quadword with new value at rising edge of clock. +// There are no internal forwarding from write to read in same clock period +// Little endian byte-addressing and unaligned access is supported +// If the address is to an output device, output will be performed instead of memory access +void memory_write(mem_p, val address, val value, bool wr_enable); + +// read 10 bytes unaligned, for instruction fetch +void memory_read_into_buffer(mem_p, val address, val bytes[], bool enable); + +// parse argument vector and load it into simulated argv area +void memory_load_argv(mem_p, int argc, char* argv[]); diff --git a/A5/registers.c b/A5/registers.c new file mode 100644 index 0000000..33b3607 --- /dev/null +++ b/A5/registers.c @@ -0,0 +1,49 @@ +/* + Implementation of x86prime 16 registers +*/ + +#include "registers.h" +#include "support.h" +#include "trace_read.h" + +#include +#include + +struct registers { + trace_p tracer; + val data[16]; +}; + +reg_p regs_create() { + reg_p regs = (reg_p) malloc(sizeof(struct registers)); + for (int i = 0; i < 16; ++i) + regs->data[i] = from_int(0); + regs->tracer = 0; + return regs; +} + +void regs_destroy(reg_p regs) { + if (regs->tracer) { + if (!trace_all_matched(regs->tracer)) + error("Parts of trace for register writes was not matched"); + trace_reader_destroy(regs->tracer); + } + free(regs); +} + +void regs_tracefile(reg_p regs, const char *filename) { + regs->tracer = trace_reader_create('R', filename); +} + +val reg_read(reg_p regs, val reg_num) { + return regs->data[reg_num.val & 0x0F]; +} + +void reg_write(reg_p regs, val reg_num, val value, bool wr_enable) { + if (wr_enable) { + if (trace_match_next(regs->tracer, reg_num, value)) + regs->data[reg_num.val & 0x0F] = value; + else + error("Trace mismatch on write to register"); + } +} diff --git a/A5/registers.h b/A5/registers.h new file mode 100644 index 0000000..260d18a --- /dev/null +++ b/A5/registers.h @@ -0,0 +1,19 @@ +/* + Registers + + A model of the 16 registers in x86prime +*/ + +#include "wires.h" + +struct registers; +typedef struct registers *reg_p; + +reg_p regs_create(); +void regs_destroy(reg_p); + +// associate with a tracefile for verification +void regs_tracefile(reg_p regs, const char *filename); + +val reg_read(reg_p regs, val reg_num); +void reg_write(reg_p regs, val reg_num, val value, bool wr_enable); diff --git a/A5/support.c b/A5/support.c new file mode 100644 index 0000000..db82501 --- /dev/null +++ b/A5/support.c @@ -0,0 +1,9 @@ +#include +#include + +#include "support.h" + +void error(const char* message) { + fprintf(stderr, "%s\n", message); + exit(-1); +} diff --git a/A5/support.h b/A5/support.h new file mode 100644 index 0000000..4f15aa6 --- /dev/null +++ b/A5/support.h @@ -0,0 +1,7 @@ +/* + Support functions. Just one for now. +*/ + +// print an error and exit +void error(const char* message); + diff --git a/A5/test.sh b/A5/test.sh new file mode 100755 index 0000000..a2781e3 --- /dev/null +++ b/A5/test.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +### USAGE +# All test must x86prime files (*.prime), located in tests +# All test must start with a label "test:" from which the test start +# You can insert a "jmp myprogram" as the first instruction, if your program starts elsewhere + +# Exit immediately if any command below fails. +set -e + +# Ensure latest version +make + +# The command with which you run PRUN: You should likely change this variable +PRUN=prun + +# The command with which you run PRASM: You should likely change this variable +PRASM=prasm + +TESTLOC=tests +TESTDIR=test_runs + +if [[ ! -d "${TESTLOC}" ]] ; then + echo "Cannot find the test location ${TESTLOC}" + exit +fi + +echo ">> I will now test all *.prime files in ${TESTLOC}/" + +echo "Generating a test_runs directory.." +mkdir -p $TESTDIR +rm -f $TESTDIR/* + +echo "Running the tests.." +exitcode=0 + +for f in tests/*.prime; do + filename=${f#"$TESTLOC/"} + fname=${filename%.prime} + + echo ">>> Testing ${filename}.." + + # Generate hex file + hexfile=$TESTDIR/$fname.hex + $PRASM $f + mv $TESTLOC/$fname.hex $TESTDIR/ + mv $TESTLOC/$fname.sym $TESTDIR/ + + # Generate trace file + tracefile=$TESTDIR/$fname.trc + $PRUN $hexfile test -tracefile $tracefile + + set +e # Continue after failed trace + ./sim $hexfile 0x0 $tracefile > $TESTDIR/$fname.out + set -e + + if [ ! $(grep Done $TESTDIR/$fname.out) ] + then + echo "Failed :-(" + echo "For details, see $TESTDIR/$fname.out" + exitcode=1 + else + echo "Success :-)" + fi +done + +exit $exitcode diff --git a/A5/trace_read.c b/A5/trace_read.c new file mode 100644 index 0000000..36aedd0 --- /dev/null +++ b/A5/trace_read.c @@ -0,0 +1,99 @@ +/* + Trace reader implementation +*/ + + +#include +#include + +#include "support.h" +#include "trace_read.h" + +typedef uint64_t word; + +struct trace_reader { + FILE* trace_file; + char filter; + bool next_valid; + word next_addr; + word next_value; +}; + + +trace_p trace_reader_create(char filter, const char* filename) { + FILE* f = fopen(filename, "r"); + if (f == NULL) + error("Failed to open tracefile"); + trace_p tracer = malloc(sizeof(struct trace_reader)); + tracer->trace_file = f; + tracer->filter = filter; + tracer->next_valid = false; + return tracer; +} + +void trace_reader_destroy(trace_p tracer) { + fclose(tracer->trace_file); + free(tracer); +} + +void get_next_if_needed_and_possible(trace_p tracer) { + if (tracer->next_valid) return; /* no need */ + int domain; + do { + domain = getc(tracer->trace_file); /* every line starts with the domain */ + if (domain == EOF) return; /* trace ended */ + int res = fscanf(tracer->trace_file, " %lx %lx ", &tracer->next_addr, &tracer->next_value); + if (res != 2) + error("Wrong trace format"); /* couldn't read the entry??? */ + } while (domain != tracer->filter); /* skip other domains */ + tracer->next_valid = true; +} + +bool trace_match_next(trace_p tracer, val address, val value) { + if (tracer == 0) return true; // no trace file - condition trivially true + get_next_if_needed_and_possible(tracer); + if (!tracer->next_valid) { + printf(" -- end of tracefile reached, while trying to match '%c' %lx <- %lx\n", + tracer->filter, address.val, value.val); + return false; + } + if (tracer->next_addr != address.val) { + printf(" -- address mismatch, access '%c' %lx, but tracefile expected address: %lx\n", + tracer->filter, address.val, tracer->next_addr); + return false; + } + if (tracer->next_value != value.val) { + printf(" -- value mismatch, access '%c' %lx\n", tracer->filter, address.val); + printf(" -- with value %lx, but tracefile expected %lx\n", value.val, tracer->next_value); + return false; + } + /* matched! */ + tracer->next_valid = false; /* we just matched it */ + return true; +} + +bool trace_match_and_get_next(trace_p tracer, val address, val* value) { + if (tracer == 0) return true; // no trace file + get_next_if_needed_and_possible(tracer); + if (!tracer->next_valid) { + printf(" -- end of tracefile reached, while trying to match '%c' %lx <- <>\n", + tracer->filter, address.val); + return false; + } + if (tracer->next_addr != address.val) { + printf(" -- address mismatch, access '%c' %lx, but tracefile expected address: %lx\n", + tracer->filter, address.val, tracer->next_addr); + return false; + } + value->val = tracer->next_value; + tracer->next_valid = false; + return true; +} + + +bool trace_all_matched(trace_p tracer) { + if (tracer == 0) return true; // no trace file - condition trivially true + get_next_if_needed_and_possible(tracer); + return !tracer->next_valid; +} + diff --git a/A5/trace_read.h b/A5/trace_read.h new file mode 100644 index 0000000..c80b1ba --- /dev/null +++ b/A5/trace_read.h @@ -0,0 +1,17 @@ +/* + trace reader. Used by memories to validate changes against a trace built by x86prime +*/ + +#include "wires.h" + +struct trace_reader; + +typedef struct trace_reader *trace_p; + +trace_p trace_reader_create(char filter, const char* filename); +void trace_reader_destroy(trace_p tracer); + +bool trace_match_next(trace_p tracer, val address, val value); // true if matched +bool trace_match_and_get_next(trace_p tracer, val address, val* value); // true if matched AND enabled + +bool trace_all_matched(trace_p tracer); diff --git a/A5/wires.c b/A5/wires.c new file mode 100644 index 0000000..7a156b2 --- /dev/null +++ b/A5/wires.c @@ -0,0 +1,98 @@ +#include +#include +#include "wires.h" + +hilo unzip(val value) { + uint64_t hi, lo, val; + val = value.val; + hi = lo = 0; + for (int i=0; i<32; i++) { + lo |= (val & 1) << i; + val >>= 1; + hi |= (val & 1) << i; + val >>= 1; + } + hilo result; + result.hi = from_int(hi); + result.lo = from_int(lo); + return result; +} + +val zip(hilo values) { + uint64_t hi = values.hi.val; + uint64_t lo = values.lo.val; + uint64_t result = 0; + for (int i=0; i<32; i++) { + result |= (hi & 1) << (2*i+1); + hi >>= 1; + result |= (lo & 1) << (2*i); + lo >>= 1; + } + return from_int(result); +} + +val pick_bits(int lsb, int sz, val value) { + uint64_t v = value.val; + v >>= lsb; + if (sz < 64) { + uint64_t mask = 1; + mask <<= sz; + mask -= 1; + v &= mask; + } + return from_int(v); +} + +val put_bits(int lsb, int sz, val value) { + val masked = pick_bits(0,sz,value); + masked.val <<= lsb; + return masked; +} + +val pick_bits_arr(int msb, int sz, val arr[]) { + if (sz > 64) { + fprintf(stderr, "error: pick_bits_arr called with sz > 64"); + exit(EXIT_FAILURE); + } + + int offset = msb % 8; + int start_byte = msb / 8; + int covered_bytes = sz / 8; + + uint64_t retval = 0; + for(int i = start_byte; i < covered_bytes; i++) { + retval += ((uint64_t) (arr[i].val)) >> (offset + i * 8); + } + + uint64_t mask = ((uint64_t)-1) >> (64 - offset); + retval &= mask; + return from_int(retval); +} + + +bool pick_one(int position, val value) { + return ((value.val >> position) & 1) == 1; +} + +val from_int(uint64_t v) { + val val; + val.val = v; + return val; +} + +val reverse_bytes(int num_bytes, val value) { + uint64_t val = value.val; + uint64_t res = 0; + while (num_bytes--) { + res <<= 8; + res |= val & 0xFF; + val >>= 8; + } + return from_int(res); +} + +val sign_extend(int sign_position, val value) { + uint64_t sign = value.val & (((uint64_t)1) << sign_position); + return from_int(value.val - (sign << 1)); +} + diff --git a/A5/wires.h b/A5/wires.h new file mode 100644 index 0000000..cd8591e --- /dev/null +++ b/A5/wires.h @@ -0,0 +1,56 @@ +/* + Elementary functions for digital logic simulation + + All functions declared here corresponds to wire connections. + No "real" computation is being carried out by them. + + A single wire, when used as a control signal, is represented by the type 'bool' + Multiple wires, or a single wire not used as a control signal, is represented + by the type 'val' + + For control signals (bools) you can use C built-in operators. +*/ + +#ifndef WIRES_H +#define WIRES_H + +#include +#include + +// A generic bitvector - max 64 bits, though. By accident sufficient for our needs :-) +typedef struct { uint64_t val; } val; + +// A pair of bitvectors - used by zip() and unzip() +typedef struct { + val hi; + val lo; +} hilo; + +// simple conversion +val from_int(uint64_t); + +// unzip pairwise into two bitvectors holding even/odd bits +hilo unzip(val); + +// zip a pair of bitvectors into one. +val zip(hilo); + +// pick a set of bits from a value +// lsb: index of least significant bit +// sz: size +val pick_bits(int lsb, int sz, val); + +// put sz least significant bits from val at position lsb in result. +// rest of result is 0. +val put_bits(int lsb, int sz, val); + +// pick a single bit from a value +bool pick_one(int position, val); + +// reverse the order of bytes within value +val reverse_bytes(int num_bytes, val value); + +// sign extend by copying a sign bit to all higher positions +val sign_extend(int sign_position, val value); + +#endif