| // 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 <verilated.h> | |
| #include "VCoreMiniAxi.h" | |
| #include "src/buses/axi-slave.h" | |
| #include "src/buses/axi.h" | |
| #include "src/buses/axilite.h" | |
| #if VM_TRACE | |
| #include <verilated_fst_c.h> | |
| #endif | |
| #include <thread> | |
| #include <signal.h> | |
| #include "tests/renode/kelvin.grpc.pb.h" | |
| #include <grpcpp/grpcpp.h> | |
| RenodeAgent* kelvin_slave; | |
| RenodeAgent* kelvin_master; | |
| auto* axi_slave = new Axi(128, 32); | |
| auto* axi_master = new AxiSlave(128, 32); | |
| VCoreMiniAxi* top = new VCoreMiniAxi; | |
| uint8_t uint8_dummy; | |
| VerilatedFstC* tfp; | |
| vluint64_t main_time = 0; | |
| vluint64_t last_tick = 0; | |
| void tick() { | |
| if (!top) { | |
| return; | |
| } | |
| // First cycle, always evaluate regardless of what role asked. | |
| if (main_time == 0) { | |
| top->io_irq = false; | |
| top->eval(); | |
| main_time++; | |
| return; | |
| } | |
| // If we've already done this tick, skip. Otherwise, tick and update `last_tick`. | |
| if (main_time == last_tick) { | |
| return; | |
| } else { | |
| // On rising-edges, check if the core is in WFI. | |
| // If so, generate an interrupt pulse to wake it. | |
| static bool irq_state = false; | |
| if (top->io_aclk) { | |
| if (top->io_wfi && !irq_state) { | |
| irq_state = true; | |
| } | |
| if (!top->io_wfi && irq_state) { | |
| irq_state = false; | |
| } | |
| } | |
| top->io_irq = irq_state; | |
| top->eval(); | |
| last_tick = main_time; | |
| main_time++; | |
| } | |
| #if VM_TRACE | |
| tfp->dump(main_time); | |
| static bool flushed_on_halt = false; | |
| if ((top->io_halted == 1 && !flushed_on_halt) || ((main_time % 1000) == 0)) { | |
| flushed_on_halt = top->io_halted; | |
| tfp->flush(); | |
| } | |
| #endif | |
| } | |
| void axiSlaveEval() { | |
| tick(); | |
| if (kelvin_slave) { | |
| kelvin_slave->handleInterrupts(); | |
| } | |
| } | |
| void axiMasterEval() { | |
| tick(); | |
| if (kelvin_master) { | |
| kelvin_master->handleInterrupts(); | |
| } | |
| } | |
| RenodeAgent* Init(bool slave) { | |
| if (slave) { | |
| kelvin_slave = new RenodeAgent(axi_slave); | |
| axi_slave->aclk = &top->io_aclk; | |
| axi_slave->aresetn = &top->io_aresetn; | |
| axi_slave->awid = &top->io_axi_slave_write_addr_bits_id; | |
| axi_slave->awaddr = &top->io_axi_slave_write_addr_bits_addr; | |
| axi_slave->awlen = &top->io_axi_slave_write_addr_bits_len; | |
| axi_slave->awsize = &top->io_axi_slave_write_addr_bits_size; | |
| axi_slave->awburst = &top->io_axi_slave_write_addr_bits_burst; | |
| axi_slave->awlock = &top->io_axi_slave_write_addr_bits_lock; | |
| axi_slave->awcache = &top->io_axi_slave_write_addr_bits_cache; | |
| axi_slave->awprot = &top->io_axi_slave_write_addr_bits_prot; | |
| axi_slave->awqos = &top->io_axi_slave_write_addr_bits_qos; | |
| axi_slave->awregion = &top->io_axi_slave_write_addr_bits_region; | |
| axi_slave->awuser = &uint8_dummy; | |
| axi_slave->awvalid = &top->io_axi_slave_write_addr_valid; | |
| axi_slave->awready = &top->io_axi_slave_write_addr_ready; | |
| axi_slave->wdata = (uint32_t*)&top->io_axi_slave_write_data_bits_data; | |
| axi_slave->wstrb = (uint8_t*)&top->io_axi_slave_write_data_bits_strb; | |
| axi_slave->wlast = &top->io_axi_slave_write_data_bits_last; | |
| axi_slave->wuser = &uint8_dummy; | |
| axi_slave->wvalid = &top->io_axi_slave_write_data_valid; | |
| axi_slave->wready = &top->io_axi_slave_write_data_ready; | |
| axi_slave->bid = &top->io_axi_slave_write_resp_bits_id; | |
| axi_slave->bresp = &top->io_axi_slave_write_resp_bits_resp; | |
| axi_slave->buser = &uint8_dummy; | |
| axi_slave->bvalid = &top->io_axi_slave_write_resp_valid; | |
| axi_slave->bready = &top->io_axi_slave_write_resp_ready; | |
| axi_slave->arid = &top->io_axi_slave_read_addr_bits_id; | |
| axi_slave->araddr = &top->io_axi_slave_read_addr_bits_addr; | |
| axi_slave->arlen = &top->io_axi_slave_read_addr_bits_len; | |
| axi_slave->arsize = &top->io_axi_slave_read_addr_bits_size; | |
| axi_slave->arburst = &top->io_axi_slave_read_addr_bits_burst; | |
| axi_slave->arlock = &top->io_axi_slave_read_addr_bits_lock; | |
| axi_slave->arcache = &top->io_axi_slave_read_addr_bits_cache; | |
| axi_slave->arprot = &top->io_axi_slave_read_addr_bits_prot; | |
| axi_slave->arqos = &top->io_axi_slave_read_addr_bits_qos; | |
| axi_slave->arregion = &top->io_axi_slave_read_addr_bits_region; | |
| axi_slave->aruser = &uint8_dummy; | |
| axi_slave->arvalid = &top->io_axi_slave_read_addr_valid; | |
| axi_slave->arready = &top->io_axi_slave_read_addr_ready; | |
| axi_slave->rid = &top->io_axi_slave_read_data_bits_id; | |
| axi_slave->rdata = (uint32_t*)&top->io_axi_slave_read_data_bits_data; | |
| axi_slave->rresp = &top->io_axi_slave_read_data_bits_resp; | |
| axi_slave->rlast = &top->io_axi_slave_read_data_bits_last; | |
| axi_slave->ruser = &uint8_dummy; | |
| axi_slave->rvalid = &top->io_axi_slave_read_data_valid; | |
| axi_slave->rready = &top->io_axi_slave_read_data_ready; | |
| axi_slave->evaluateModel = &axiSlaveEval; | |
| return kelvin_slave; | |
| } else { | |
| kelvin_master = new RenodeAgent(axi_master); | |
| kelvin_slave->addBus(axi_master); | |
| axi_master->aclk = &top->io_aclk; | |
| axi_master->aresetn = &top->io_aresetn; | |
| axi_master->awid = &top->io_axi_master_write_addr_bits_id; | |
| axi_master->awaddr = &top->io_axi_master_write_addr_bits_addr; | |
| axi_master->awlen = &top->io_axi_master_write_addr_bits_len; | |
| axi_master->awsize = &top->io_axi_master_write_addr_bits_size; | |
| axi_master->awburst = &top->io_axi_master_write_addr_bits_burst; | |
| axi_master->awlock = &top->io_axi_master_write_addr_bits_lock; | |
| axi_master->awcache = &top->io_axi_master_write_addr_bits_cache; | |
| axi_master->awprot = &top->io_axi_master_write_addr_bits_prot; | |
| axi_master->awqos = &top->io_axi_master_write_addr_bits_qos; | |
| axi_master->awregion = &top->io_axi_master_write_addr_bits_region; | |
| axi_master->awuser = &uint8_dummy; | |
| axi_master->awvalid = &top->io_axi_master_write_addr_valid; | |
| axi_master->awready = &top->io_axi_master_write_addr_ready; | |
| axi_master->wdata = (uint32_t*)&top->io_axi_master_write_data_bits_data; | |
| axi_master->wstrb = (uint8_t*)&top->io_axi_master_write_data_bits_strb; | |
| axi_master->wlast = &top->io_axi_master_write_data_bits_last; | |
| axi_master->wuser = &uint8_dummy; | |
| axi_master->wvalid = &top->io_axi_master_write_data_valid; | |
| axi_master->wready = &top->io_axi_master_write_data_ready; | |
| axi_master->bid = &top->io_axi_master_write_resp_bits_id; | |
| axi_master->bresp = &top->io_axi_master_write_resp_bits_resp; | |
| axi_master->buser = &uint8_dummy; | |
| axi_master->bvalid = &top->io_axi_master_write_resp_valid; | |
| axi_master->bready = &top->io_axi_master_write_resp_ready; | |
| axi_master->arid = &top->io_axi_master_read_addr_bits_id; | |
| axi_master->araddr = &top->io_axi_master_read_addr_bits_addr; | |
| axi_master->arlen = &top->io_axi_master_read_addr_bits_len; | |
| axi_master->arsize = &top->io_axi_master_read_addr_bits_size; | |
| axi_master->arburst = &top->io_axi_master_read_addr_bits_burst; | |
| axi_master->arlock = &top->io_axi_master_read_addr_bits_lock; | |
| axi_master->arcache = &top->io_axi_master_read_addr_bits_cache; | |
| axi_master->arprot = &top->io_axi_master_read_addr_bits_prot; | |
| axi_master->arqos = &top->io_axi_master_read_addr_bits_qos; | |
| axi_master->arregion = &top->io_axi_master_read_addr_bits_region; | |
| axi_master->aruser = &uint8_dummy; | |
| axi_master->arvalid = &top->io_axi_master_read_addr_valid; | |
| axi_master->arready = &top->io_axi_master_read_addr_ready; | |
| axi_master->rid = &top->io_axi_master_read_data_bits_id; | |
| axi_master->rdata = (uint32_t*)&top->io_axi_master_read_data_bits_data; | |
| axi_master->rresp = &top->io_axi_master_read_data_bits_resp; | |
| axi_master->rlast = &top->io_axi_master_read_data_bits_last; | |
| axi_master->ruser = &uint8_dummy; | |
| axi_master->rvalid = &top->io_axi_master_read_data_valid; | |
| axi_master->rready = &top->io_axi_master_read_data_ready; | |
| axi_master->evaluateModel = &axiMasterEval; | |
| return kelvin_master; | |
| } | |
| } | |
| // Stub to support linking with Renode's integration library. | |
| // Only used for the shared library version, which we do not support. | |
| RenodeAgent* Init() { | |
| abort(); | |
| return nullptr; | |
| } | |
| std::thread kelvin_slave_thread; | |
| std::thread kelvin_master_thread; | |
| std::string kelvin_slave_address; | |
| std::string kelvin_master_address; | |
| class KelvinServiceImpl final : public kelvin::Kelvin::Service { | |
| grpc::Status StartAgent( | |
| grpc::ServerContext* context, | |
| const kelvin::StartAgentRequest* request, | |
| kelvin::StartAgentResponse* response) override { | |
| auto type = request->type(); | |
| auto receiverPort = request->receiverport(); | |
| auto senderPort = request->senderport(); | |
| auto address = request->address().c_str(); | |
| switch (type) { | |
| case kelvin::AgentType::Slave: { | |
| kelvin_slave_address = address; | |
| Init(true); | |
| kelvin_slave_thread = std::thread([=]() { | |
| kelvin_slave->simulate(receiverPort, senderPort, kelvin_slave_address.c_str()); | |
| }); | |
| } | |
| break; | |
| case kelvin::AgentType::Master: { | |
| kelvin_master_address = address; | |
| Init(false); | |
| kelvin_master_thread = std::thread([=]() { | |
| kelvin_master->simulate(receiverPort, senderPort, kelvin_master_address.c_str()); | |
| }); | |
| } | |
| break; | |
| default: | |
| break; | |
| } | |
| return grpc::Status::OK; | |
| } | |
| }; | |
| std::unique_ptr<grpc::Server> server; | |
| // SIGTERM handler to shut down gRPC. | |
| // NB: We spawn a new thread to call shutdown, as | |
| // it must not be on the same thread as the server. | |
| void sigterm_handler(int signo, siginfo_t* info, void* context) { | |
| std::thread([]() { | |
| server->Shutdown(); | |
| }).join(); | |
| } | |
| extern "C" int main(int argc, char** argv) { | |
| #if VM_TRACE | |
| Verilated::traceEverOn(true); | |
| tfp = new VerilatedFstC; | |
| top->trace(tfp, 99); | |
| tfp->open("/tmp/core_mini_axi.fst"); | |
| #endif | |
| // TODO(atv): How to fix this port? | |
| std::string server_address = "127.0.0.1:9003"; | |
| KelvinServiceImpl service; | |
| grpc::ServerBuilder builder; | |
| builder.AddListeningPort(server_address, grpc::InsecureServerCredentials()); | |
| builder.RegisterService(&service); | |
| server = builder.BuildAndStart(); | |
| Verilated::commandArgs(argc, argv); | |
| // Setup a SIGTERM handler to shut down gRPC. | |
| struct sigaction act = {0}; | |
| act.sa_flags = SA_SIGINFO; | |
| act.sa_sigaction = sigterm_handler; | |
| sigaction(SIGTERM, &act, NULL); | |
| server->Wait(); | |
| if (kelvin_master_thread.joinable()) | |
| kelvin_master_thread.join(); | |
| if (kelvin_slave_thread.joinable()) | |
| kelvin_slave_thread.join(); | |
| top->final(); | |
| #if VM_TRACE | |
| if (tfp) { | |
| tfp->close(); | |
| tfp = nullptr; | |
| } | |
| #endif | |
| return 0; | |
| } |