| // Copyright 2024 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include <fcntl.h> |
| #include <sys/mman.h> |
| #include <sys/stat.h> |
| |
| #include <algorithm> |
| |
| #include "VChAI.h" // Generated |
| #include "absl/flags/flag.h" |
| #include "absl/flags/parse.h" |
| #include "absl/flags/usage.h" |
| #include "absl/log/check.h" |
| #include "absl/log/log.h" |
| #include "tests/verilator_sim/sysc_module.h" |
| #include "tests/verilator_sim/sysc_tb.h" |
| |
| ABSL_FLAG(int, cycles, 10'000'000, "Simulation cycles"); |
| ABSL_FLAG(bool, trace, false, "Enable tracing"); |
| |
| namespace { |
| |
| struct ChAI_tb : Sysc_tb { |
| sc_in<bool> io_halted; |
| sc_in<bool> io_fault; |
| using Sysc_tb::Sysc_tb; // constructor |
| |
| void posedge() { |
| check(!io_fault, "io_fault"); |
| if (io_halted) sc_stop(); |
| } |
| }; |
| |
| struct Memory : Sysc_module { |
| sc_out<sc_bv<17> > write_address; |
| sc_out<bool> write_enable; |
| sc_out<sc_bv<256> > write_data; |
| sc_out<bool> loadedn; |
| |
| sc_bv<256> wdata = 0; |
| |
| Memory(sc_module_name n, const char* path) |
| : Sysc_module(n), path_(path), offset_(0) { |
| int fd = open(path, 0); |
| CHECK(fd > 0); |
| struct stat sb; |
| CHECK(fstat(fd, &sb) == 0); |
| LOG(INFO) << "Input file size: " << sb.st_size; |
| size_ = sb.st_size; |
| if (size_ % 256 != 0) { |
| LOG(FATAL) << "Please align your file size to 256 bytes."; |
| } |
| void* data = mmap(nullptr, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); |
| CHECK(data != MAP_FAILED); |
| close(fd); |
| data_ = reinterpret_cast<uint8_t*>(data); |
| data32_ = reinterpret_cast<uint32_t*>(data_); |
| } |
| |
| ~Memory() { |
| LOG(INFO) << "Cycles at teardown: " << cycle_; |
| munmap(data_, size_); |
| data_ = nullptr; |
| data32_ = nullptr; |
| } |
| |
| void eval() { |
| if (reset) { |
| cycle_ = 0; |
| loadedn = true; |
| write_address = 0; |
| write_enable = false; |
| write_data = 0; |
| } |
| if (clock->posedge()) { |
| cycle_++; |
| if (offset_ == size_) { |
| static bool logged = false; |
| if (!logged) { |
| LOG(INFO) << "[" << cycle_ << "] setting loadedn to false"; |
| logged = true; |
| } |
| loadedn = false; |
| write_enable = false; |
| } |
| if (offset_ < size_) { |
| const size_t wordsPerWrite = 32 / sizeof(uint32_t); // 32B / 4B |
| for (size_t i = 0; i < wordsPerWrite; i++) { |
| uint32_t val = data32_[(offset_ / sizeof(uint32_t)) + i]; |
| wdata.set_word(i, val); |
| } |
| write_data.write(wdata); |
| write_enable = true; |
| write_address = offset_ >> 5; |
| offset_ += 32; // 32 bytes == 8 words |
| } |
| } |
| if (cycle_ % 10000 == 0) { |
| LOG(INFO) << "Cycle " << cycle_; |
| } |
| } |
| |
| private: |
| uint32_t cycle_ = 0; |
| const char* path_; |
| size_t offset_; // bytes |
| size_t size_; // bytes |
| uint8_t* data_; // bytes |
| uint32_t* data32_; // words |
| }; |
| |
| struct Uart : Sysc_module { |
| sc_in<bool> rx; |
| sc_out<bool> tx; |
| Uart(sc_module_name n) : Sysc_module(n) {} |
| uint64_t kBaudrate = 115200; |
| uint64_t kFrequencyHz = 10000000; // 10MHz |
| uint32_t nco_rx = static_cast<uint32_t>((kBaudrate << 20) / kFrequencyHz); |
| void eval() { |
| if (reset) { |
| last_rx_val_ = true; |
| uart_baud_ctr_ = 0; |
| baud_cnt_ = 0; |
| s_ = State::sIdle; |
| bit_in_pkt_ = 0; |
| rx_data_ = 0; |
| } |
| if (clock->posedge()) { |
| if (uart_baud_ctr_ & 0x10000) { |
| baud_cnt_++; |
| } |
| if (baud_cnt_ == 16) { |
| bool rx_val = rx; |
| bool edge = rx_val != last_rx_val_; |
| switch (s_) { |
| case State::sIdle: { |
| if (edge) { |
| s_ = State::sStarted; |
| bit_in_pkt_ = 0; |
| } |
| break; |
| } |
| case State::sStarted: { |
| if (bit_in_pkt_ == 8) { |
| LOG(INFO) << "UART val: " << (char)rx_data_; |
| rx_data_ = 0; |
| s_ = State::sIdle; |
| } |
| rx_data_ = (rx_data_ >> 1) | ((uint8_t)rx_val << 7); |
| bit_in_pkt_++; |
| break; |
| } |
| } |
| last_rx_val_ = rx_val; |
| baud_cnt_ = 0; |
| } |
| uart_baud_ctr_ = (uart_baud_ctr_ & 0xFFFF) + nco_rx; |
| } |
| } |
| |
| private: |
| enum class State { sIdle, sStarted }; |
| bool last_rx_val_ = true; |
| size_t uart_baud_ctr_ = 0; |
| size_t baud_cnt_ = 0; |
| State s_ = State::sIdle; |
| size_t bit_in_pkt_ = 0; |
| uint8_t rx_data_ = 0; |
| }; |
| |
| void ChAI_run(const char* name, const char* path, const int cycles, |
| const bool trace) { |
| VChAI chai(name); |
| ChAI_tb tb("ChAI_tb", cycles, /* random= */ false); |
| Memory mem("ChAI_mem", path); |
| Uart uart("ChAI_uart"); |
| |
| sc_signal<bool> io_halted, io_fault; |
| sc_signal<sc_bv<17> > io_sram_write_address; |
| sc_signal<bool> io_sram_write_enable; |
| sc_signal<sc_bv<256> > io_sram_write_data; |
| sc_signal<bool> mem_loadedn; |
| sc_signal<bool> uart_tx; // Output from ChAI |
| sc_signal<bool> uart_rx; // Input to ChAI |
| |
| tb.io_halted(io_halted); |
| tb.io_fault(io_fault); |
| |
| chai.io_clk_i(tb.clock); |
| chai.io_rst_ni(tb.resetn); |
| chai.io_sram_write_address(io_sram_write_address); |
| chai.io_sram_write_enable(io_sram_write_enable); |
| chai.io_sram_write_data(io_sram_write_data); |
| chai.io_finish(io_halted); |
| chai.io_fault(io_fault); |
| chai.io_freeze(mem_loadedn); |
| chai.io_uart_tx(uart_tx); |
| chai.io_uart_rx(uart_rx); |
| |
| mem.clock(tb.clock); |
| mem.reset(tb.reset); |
| mem.write_address(io_sram_write_address); |
| mem.write_enable(io_sram_write_enable); |
| mem.write_data(io_sram_write_data); |
| mem.loadedn(mem_loadedn); |
| |
| uart.clock(tb.clock); |
| uart.reset(tb.reset); |
| uart.rx(uart_tx); |
| uart.tx(uart_rx); |
| |
| if (trace) { |
| tb.trace(chai); |
| } |
| |
| tb.start(); |
| } |
| |
| } // namespace |
| |
| extern "C" int sc_main(int argc, char** argv) { |
| absl::SetProgramUsageMessage("ChAI sim"); |
| auto out_args = absl::ParseCommandLine(argc, argv); |
| argc = out_args.size(); |
| argv = &out_args[0]; |
| if (argc < 2) { |
| LOG(FATAL) << "Need an input file"; |
| } |
| const char* path = argv[1]; |
| ChAI_run(Sysc_tb::get_name(argv[0]), path, absl::GetFlag(FLAGS_cycles), |
| absl::GetFlag(FLAGS_trace)); |
| return 0; |
| } |