// Header for Chisel emulator API #ifndef __IS_EMULATOR_API__ #define __IS_EMULATOR_API__ #include "emulator_mod.h" #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-function" #pragma GCC diagnostic ignored "-Wsign-compare" #pragma GCC diagnostic ignored "-Wunused-parameter" #pragma GCC diagnostic ignored "-Wunused-variable" #include #include #include #include #include /** * Converts an integer to a std::string without needing additional libraries * or C++11. */ static std::string itos(int in) { std::stringstream out; out << in; return out.str(); } /** * Copy one val_t array to another. * nb must be the exact number of bits the val_t represents. */ static __attribute__((unused)) void val_cpy(val_t* dst, val_t* src, int nb) { for (int i=0; i bool dat_from_str(std::string in, dat_t& res, int pos = 0) { int radix = 10; int negate = 0; /* Handle leading minus sign. */ if (!in.substr(pos, 1).compare("-")) { pos++; negate = 1; } if (!in.substr(pos, 1).compare("d")) { radix = 10; pos++; } else if (!in.substr(pos, 1).compare("h") || !in.substr(pos, 1).compare("x")) { radix = 16; pos++; } else if (!in.substr(pos, 2).compare("0h") || !in.substr(pos, 2).compare("0x")) { radix = 16; pos += 2; } else if (!in.substr(pos, 1).compare("b")) { radix = 2; pos++; } else if (!in.substr(pos, 2).compare("0b")) { radix = 2; pos += 2; } const int log_max_radix = 4; assert(radix <= (1 << log_max_radix)); dat_t curr_base = 1; res = 0; for (int rpos=in.length()-1; rpos>=pos; rpos--) { char c = in[rpos]; val_t c_val = 0; if (c == '_') { continue; } if (c >= '0' && c <= '9') { c_val = c - '0'; } else if (c >= 'a' && c <= 'z') { c_val = c - 'a' + 10; } else if (c >= 'A' && c <= 'Z') { c_val = c - 'A' + 10; } else { std::cerr << "dat_from_str: Invalid character '" << c << "' in '" << in << "'" << std::endl; return false; } if (c_val > radix /* || c_val < 0 */) { std::cerr << "dat_from_str: Invalid character '" << c << "'" << std::endl; return false; } dat_t temp_prod = curr_base * dat_t(c_val); res = res + temp_prod; curr_base = curr_base * dat_t(radix); } if (negate) { res = -res; } return true; } // API base class, providing common functions class api_base { public: api_base(const char* new_name, const char* new_path) : name(new_name), path(new_path) {} // returns the fully qualified name of this object (path + dot + name) std::string get_pathname() { if (*path == '\0') { return name; } else { return get_path() + "." + name; } } // returns the short name of this object std::string get_name() { return name; } // returns the path of this object (without a trailing dot) std::string get_path() { return path; } protected: const char* name; const char* path; }; // API base (non width templated) class for API accessors to dat_t class dat_api_base : public api_base { public: dat_api_base(const char* new_name, const char* new_path) : api_base(new_name, new_path) {} // returns the value of this wire as a string, or empty string on failure virtual std::string get_value() = 0; // sets the value of this wire from a string, returning true on success virtual bool set_value(std::string value) = 0; // returns the bitwidth of this wire virtual std::string get_width() = 0; }; // dat_api dummy class, does nothing except for return errors // to be used when a real dat_api object can't be found class dat_dummy : public dat_api_base { public: dat_dummy() : dat_api_base("error", "") {} std::string get_value() { return "error"; } bool set_value(std::string value) { return false; } std::string get_width() { return "error"; } }; template class dat_api : public dat_api_base { public: dat_api(dat_t* new_dat, const char* new_name, const char* new_path) : dat_api_base(new_name, new_path), dat_ptr(new_dat) {} std::string get_value() { return dat_ptr->to_str(); } bool set_value(std::string value) { return dat_from_str(value, *dat_ptr); } std::string get_width() { return itos(w); } protected: dat_t* dat_ptr; }; // API base (non width/depth templated) class for API accessors to mem_t class mem_api_base : public api_base { public: mem_api_base(const char* new_name, const char* new_path) : api_base(new_name, new_path) {} // return the value of an element as a string, or empty string on failure virtual std::string get_element(std::string index) = 0; // sets the value of an element from a string, returning true on success virtual bool set_element(std::string index, std::string value) = 0; // returns the bitwidth of a memory element virtual std::string get_width() = 0; // returns the number of memory elements virtual std::string get_depth() = 0; }; // mem_api dummy class, does nothing except for return errors // to be used when a real mem_api object can't be found class mem_dummy : public mem_api_base { public: mem_dummy() : mem_api_base("error", "") {} string get_element(std::string index) { return "error"; } bool set_element(std::string index, std::string value) { return false; } std::string get_width() { return "error"; } std::string get_depth() { return "error"; } }; template class mem_api : public mem_api_base { public: mem_api(mem_t* new_mem, const char* new_name, const char* new_path) : mem_api_base(new_name, new_path), mem_ptr(new_mem) {} string get_element(std::string index) { int index_int = atoi(index.c_str()); return mem_ptr->contents[index_int].to_str(); } bool set_element(std::string index, std::string value) { int index_int = atoi(index.c_str()); return dat_from_str(value, mem_ptr->contents[index_int]); } std::string get_width() { return itos(w); } std::string get_depth() { return itos(d); } protected: mem_t* mem_ptr; }; class mod_api_t { public: mod_api_t(): teefile(NULL) {} void init(mod_t* new_module) { module = new_module; init_mapping_table(); } void set_teefile(FILE* new_teefile) { teefile = new_teefile; } mod_t* get_module() { return module; } // API basic functions std::string get_host_name() {return "C++ Emulator API";} std::string get_api_version() {return "0";} std::string get_api_support() {return "PeekPoke Introspection";} // External access functions & helpers std::vector< std::string > tokenize(std::string str) { std::vector< std::string > res; int i = 0; int c = ' '; while ( i < str.size() ) { while (isspace(c)) { if (i >= str.size()) return res; c = str[i++]; } std::string s; while (!isspace(c) && i < str.size()) { s.push_back(c); c = str[i++]; } if (i >= str.size()) s.push_back(c); if (s.size() > 0) res.push_back(s); } return res; } // helper to verify command length, returning false and printing an error // to stderr if the length isn't in the specified range bool check_command_length(std::vector& tokenized_command, int min_args, int max_args=-1) { if (tokenized_command.size() - 1 < min_args) { std::cerr << tokenized_command[0] << " expects at least " << min_args << " args, got " << tokenized_command.size() - 1 << std::endl; return false; } else if (max_args >= 0 && tokenized_command.size() - 1 > max_args) { std::cerr << tokenized_command[0] << " expects at most " << max_args << " args, got " << tokenized_command.size() - 1 << std::endl; return false; } return true; } // Evaluates an API command, returning the reply as a string (without // the trailing newline). // Errors return "error", printing a more detailed description to stderr. // TODO: find a way to pass errors in-line, so transport layers other than // stdin/stdout (like TCP/IP) are possible while also communicating errors. std::string eval_command(string command) { std::vector tokens = tokenize(command); if (tokens.size() == 0) { std::cerr << "Empty command: '" << command << "'" << std::endl; return "error"; } if (tokens[0] == "get_host_name") { // IN: get_host_name // OUT: API host's name (arbitrary string) if (!check_command_length(tokens, 0, 0)) { return "error"; } return get_host_name(); } else if (tokens[0] == "get_api_version") { // BETA FUNCTION: semantics subject to change, use with caution // IN: get_api_version // OUT: API version supported by this host if (!check_command_length(tokens, 0, 0)) { return "error"; } return get_api_version(); } else if (tokens[0] == "get_api_support") { // BETA FUNCTION: semantics subject to change, use with caution // IN: get_api_support // OUT: list of supported API features if (!check_command_length(tokens, 0, 0)) { return "error"; } return get_api_support(); } else if (tokens[0] == "clock") { // BETA FUNCTION: semantics subject to change, use with caution // IN: clock // OUT: actual number of cycles stepped if (!check_command_length(tokens, 1, 1)) { return "error"; } int cycles = atoi(tokens[1].c_str()); module->propagate_changes(); for (int i=0; iclock(dat_t<1>(0)); } return itos(cycles); } else if (tokens[0] == "tick") { // BETA FUNCTION: semantics subject to change, use with caution // IN: tick // OUT: ok (on success) // Update registers without propagation // updating registers. module->clock_hi(dat_t<1>(0)); return "ok"; } else if (tokens[0] == "propagate") { // BETA FUNCTION: semantics subject to change, use with caution // IN: propagate // OUT: ok (on success) // This function propagates the combinational logic, without // updating registers. module->propagate_changes(); return "ok"; } else if (tokens[0] == "step") { // IN: step // OUT: actual number of cycles stepped if (!check_command_length(tokens, 1, 1)) { return "error"; } int n = atoi(tokens[1].c_str()); module->propagate_changes(); int ret = module->step(false, n); // Do we have print output to report? int nBytes = module->has_output(); if (nBytes > 0) { cout << "PRINT" << " " << nBytes << " " << module->drain_output(); } return itos(ret); } else if (tokens[0] == "set_clocks") { // BETA FUNCTION: semantics subject to change, use with caution // IN: set_clocks // OUT: ??? // I'm not really sure what this is supposed to do, but it was // in the old command API, so it's here now std::vector< int > periods; for (int i = 1; i < tokens.size(); i++) { int period = atoi(tokens[i].c_str()); periods.push_back(period); } module->setClocks(periods); return "ok"; } else if (tokens[0] == "reset") { // IN: reset // OUT: actual number of cycles in reset if (!check_command_length(tokens, 0, 1)) { return "error"; } int cycles = 1; if (tokens.size() >= 2) { cycles = atoi(tokens[1].c_str()); } for (int i=0; iclock_lo(dat_t<1>(1)); module->clock_hi(dat_t<1>(1)); } module->clock_lo(dat_t<1>(0)); return itos(cycles); } else if (tokens[0] == "peek") { // LEGACY FUNCTION: do not use in new code // IN: peek | peek // OUT: value if (!check_command_length(tokens, 1, 2)) { return "error"; } cerr << "peek is deprecated, use wire_peek or mem_peek" << std::endl; module->propagate_changes(); if (tokens.size() == 2) { return get_dat_by_name(tokens[1])->get_value(); } else if (tokens.size() == 3) { return get_mem_by_name(tokens[1])->get_element(tokens[2]); } } else if (tokens[0] == "poke") { // LEGACY FUNCTION: do not use in new code // IN: poke | poke // OUT: true (on success), false (on failure) if (!check_command_length(tokens, 2, 3)) { return ""; } cerr << "poke is deprecated, use wire_poke or mem_poke" << std::endl; bool success; if (tokens.size() == 3) { success = get_dat_by_name(tokens[1])->set_value(tokens[2]); } else if (tokens.size() == 4) { success = get_mem_by_name(tokens[1])->set_element(tokens[2], tokens[3]); } std::string result; if (success) { result = "true"; module->mark_stale(); } else { result = "false"; } return result; } else if (tokens[0] == "wire_peek") { // IN: wire_peek // OUT: value if (!check_command_length(tokens, 1, 1)) { return "error"; } module->propagate_changes(); return get_dat_by_name(tokens[1])->get_value(); } else if (tokens[0] == "wire_poke") { // IN: wire_poke // OUT: ok (on success) if (!check_command_length(tokens, 2, 2)) { return "error"; } bool success = get_dat_by_name(tokens[1])->set_value(tokens[2]); std::string result; if (success) { result = "ok"; module->mark_stale(); } else { result = "error"; } return result; } else if (tokens[0] == "mem_peek") { // IN: mem_peek // OUT: value if (!check_command_length(tokens, 2, 2)) { return "error"; } module->propagate_changes(); return get_mem_by_name(tokens[1])->get_element(tokens[2]); } else if (tokens[0] == "mem_poke") { // IN: mem_poke // OUT: ok (on success) if (!check_command_length(tokens, 3, 3)) { return "error"; } bool success = get_mem_by_name(tokens[1])->set_element(tokens[2], tokens[3]); std::string result; if (success) { result = "ok"; module->mark_stale(); } else { result = "error"; } return result; return success ? "ok" : "error"; } else if (tokens[0] == "trace") { // IN: trace n + // OUT: values // TODO: ADD MEM PEEK SUPPORT stringstream ss; if (!check_command_length(tokens, 2)) { return "bad"; } int n = atoi(tokens[1].c_str()); for (int t = 0; t < n; t++) { for (int i = 2; i < tokens.size(); i++) ss << " " << get_dat_by_name(tokens[i])->get_value(); int ret = module->step(false, 1); // if (!ret) // return "error"; } return ss.str(); } else if (tokens[0] == "list_wires") { // IN: list_wires // OUT: list of wires if (!check_command_length(tokens, 0, 0)) { return "error"; } std::string out = ""; for (std::map::iterator it = dat_table.begin(); it != dat_table.end(); it++) { out = out + it->second->get_pathname() + " "; } if (out.size() >= 1) { return out.substr(0, out.size() - 1); } else { return ""; } } else if (tokens[0] == "list_mems") { // IN: list_mems // OUT: list of memories if (!check_command_length(tokens, 0, 0)) { return "error"; } std::string out = ""; for (std::map::iterator it = mem_table.begin(); it != mem_table.end(); it++) { out = out + it->second->get_pathname() + " "; } if (out.size() >= 1) { return out.substr(0, out.size() - 1); } else { return ""; } } else if (tokens[0] == "wire_width") { // IN: wire_width // OUT: bitwidth of wire if (!check_command_length(tokens, 1, 1)) { return "error"; } return get_dat_by_name(tokens[1])->get_width(); } else if (tokens[0] == "mem_width") { // IN: mem_width // OUT: bitwidth of memory element if (!check_command_length(tokens, 1, 1)) { return "error"; } return get_mem_by_name(tokens[1])->get_width(); } else if (tokens[0] == "mem_depth") { // IN: mem_depth // OUT: elements in memory if (!check_command_length(tokens, 1, 1)) { return "error"; } return get_mem_by_name(tokens[1])->get_depth(); } else if (tokens[0] == "referenced_snapshot_save") { // BETA FUNCTION: semantics subject to change, use with caution // IN: referenced_snapshot_save // OUT: Reference name (an arbitrary string) for saved snapshot // of current state, should be equivalent to the input. // Caution: the state may not be self-consistent (i.e. clk_lo // does not need to have been applied before this, and calls to // clk_lo immediately after restoring may change the state). if (!check_command_length(tokens, 1, 1)) { return "error"; } module->propagate_changes(); mod_t *snapshot = module->clone(); snapshot_table[tokens[1]] = snapshot; return tokens[1]; } else if (tokens[0] == "referenced_snapshot_restore") { // BETA FUNCTION: semantics subject to change, use with caution // IN: referenced_snapshot_restore // OUT: ok (on success) if (!check_command_length(tokens, 1, 1)) { return "error"; } mod_t *snapshot = get_snapshot_by_reference(tokens[1]); if (snapshot == NULL) { return "error"; } bool success = module->set_circuit_from(snapshot); std::string result; if (success) { result = "ok"; module->mark_stale(); } else { result = "error"; } return result; } else { std::cerr << "Unknown command: '" << tokens[0] << "'" << std::endl; } return "error"; } void read_eval_print_loop() { while (true) { std::string str_in; do { std::getline(cin, str_in); } while (cin.fail() && errno == EINTR); if (!cin.good()) { break; } if (teefile != NULL) { fprintf(teefile, "%s\n", str_in.c_str()); fflush(teefile); } if (str_in == "quit") { break; } else { cout << eval_command(str_in) << std::endl; } } } protected: FILE* teefile; mod_t* module; // Mapping table functions virtual void init_mapping_table() = 0; dat_api_base* get_dat_by_name(std::string name) { if (dat_table.find(name.c_str()) != dat_table.end()) { return dat_table[name.c_str()]; } else { std::cerr << "Unable to find dat '" << name << "'" << std::endl; return &this_dat_dummy; } } mem_api_base* get_mem_by_name(std::string name) { if (mem_table.find(name.c_str()) != mem_table.end()) { return mem_table[name.c_str()]; } else { std::cerr << "Unable to find mem '" << name << "'" << std::endl; return &this_mem_dummy; } } mod_t* get_snapshot_by_reference(std::string name) { if (snapshot_table.find(name) != snapshot_table.end()) { return snapshot_table[name]; } else { std::cerr << "Unable to find snapshot reference '" << name << "'" << std::endl; return NULL; } } class string_comparator { public: bool operator()(const char* x, const char* y) const { return strcmp(x, y) < 0; } }; std::map dat_table; std::map mem_table; // TODO: replace the dummy with explicit NULL checks - this is simple // but a bit inelegant dat_dummy this_dat_dummy; mem_dummy this_mem_dummy; // Snapshot functions std::map snapshot_table; }; #pragma GCC diagnostic pop #endif