A5
This commit is contained in:
14
A5/Makefile
Normal file
14
A5/Makefile
Normal file
@ -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
|
108
A5/arithmetic.c
Normal file
108
A5/arithmetic.c
Normal file
@ -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;
|
||||
}
|
35
A5/arithmetic.h
Normal file
35
A5/arithmetic.h
Normal file
@ -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);
|
||||
|
||||
|
141
A5/compute.c
Normal file
141
A5/compute.c
Normal file
@ -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 <stdio.h>
|
||||
/*
|
||||
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;
|
||||
}
|
||||
*/
|
40
A5/compute.h
Normal file
40
A5/compute.h
Normal file
@ -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);
|
||||
|
||||
|
||||
|
||||
|
48
A5/ip_reg.c
Normal file
48
A5/ip_reg.c
Normal file
@ -0,0 +1,48 @@
|
||||
/*
|
||||
Implementation of x86prime 16 registers
|
||||
*/
|
||||
|
||||
#include "ip_reg.h"
|
||||
#include "support.h"
|
||||
#include "trace_read.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
17
A5/ip_reg.h
Normal file
17
A5/ip_reg.h
Normal file
@ -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);
|
247
A5/main.c
Normal file
247
A5/main.c
Normal file
@ -0,0 +1,247 @@
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#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");
|
||||
}
|
250
A5/memory.c
Normal file
250
A5/memory.c
Normal file
@ -0,0 +1,250 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#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);
|
||||
}
|
||||
}
|
38
A5/memory.h
Normal file
38
A5/memory.h
Normal file
@ -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[]);
|
49
A5/registers.c
Normal file
49
A5/registers.c
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
Implementation of x86prime 16 registers
|
||||
*/
|
||||
|
||||
#include "registers.h"
|
||||
#include "support.h"
|
||||
#include "trace_read.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
19
A5/registers.h
Normal file
19
A5/registers.h
Normal file
@ -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);
|
9
A5/support.c
Normal file
9
A5/support.c
Normal file
@ -0,0 +1,9 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "support.h"
|
||||
|
||||
void error(const char* message) {
|
||||
fprintf(stderr, "%s\n", message);
|
||||
exit(-1);
|
||||
}
|
7
A5/support.h
Normal file
7
A5/support.h
Normal file
@ -0,0 +1,7 @@
|
||||
/*
|
||||
Support functions. Just one for now.
|
||||
*/
|
||||
|
||||
// print an error and exit
|
||||
void error(const char* message);
|
||||
|
67
A5/test.sh
Executable file
67
A5/test.sh
Executable file
@ -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
|
99
A5/trace_read.c
Normal file
99
A5/trace_read.c
Normal file
@ -0,0 +1,99 @@
|
||||
/*
|
||||
Trace reader implementation
|
||||
*/
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#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 <- <<some value>>\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;
|
||||
}
|
||||
|
17
A5/trace_read.h
Normal file
17
A5/trace_read.h
Normal file
@ -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);
|
98
A5/wires.c
Normal file
98
A5/wires.c
Normal file
@ -0,0 +1,98 @@
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#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));
|
||||
}
|
||||
|
56
A5/wires.h
Normal file
56
A5/wires.h
Normal file
@ -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 <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// 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
|
Reference in New Issue
Block a user