Compare commits
No commits in common. "7af85f5601688caf7807d8e9a64c51897e854c30" and "38e7fc464697ca397a0cfc34f9de1159ed8eee64" have entirely different histories.
7af85f5601
...
38e7fc4646
|
@ -9,7 +9,3 @@ This is based off [cnlohr/mini-rv32ima](https://github.com/cnlohr/mini-rv32ima),
|
||||||
- Moved *ALL* device and MMIO code to seperate interfaces
|
- Moved *ALL* device and MMIO code to seperate interfaces
|
||||||
|
|
||||||
Depends on lucore.
|
Depends on lucore.
|
||||||
|
|
||||||
# Usage
|
|
||||||
|
|
||||||
TBD (if this is moved to another repo). See the riscv_test_harness project.
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ namespace riscv {
|
||||||
|
|
||||||
/// The CPU core.
|
/// The CPU core.
|
||||||
struct CPU : Bus::Device {
|
struct CPU : Bus::Device {
|
||||||
BasicType Type() const override { return BasicType::Cpu; }
|
|
||||||
bool Clocked() const override { return true; }
|
bool Clocked() const override { return true; }
|
||||||
void Clock() override;
|
void Clock() override;
|
||||||
|
|
||||||
|
@ -16,17 +15,6 @@ namespace riscv {
|
||||||
|
|
||||||
void TimerInterrupt();
|
void TimerInterrupt();
|
||||||
|
|
||||||
constexpr CPU() {
|
|
||||||
Reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr void Reset() {
|
|
||||||
// Initalize some state. We're cool like that :)
|
|
||||||
pc = 0x80000000;
|
|
||||||
gpr[Gpr::A0] = 0x0; // HART id
|
|
||||||
extraflags |= 3; // Start in Machine mode
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Handlers for CSR read/write (if we need it?)
|
// TODO: Handlers for CSR read/write (if we need it?)
|
||||||
|
|
||||||
/// CPU state
|
/// CPU state
|
||||||
|
@ -57,7 +45,9 @@ namespace riscv {
|
||||||
/// Set by [CPU::Trap] for the trap code.
|
/// Set by [CPU::Trap] for the trap code.
|
||||||
u32 trapCode { 0 };
|
u32 trapCode { 0 };
|
||||||
|
|
||||||
void Step(u32 instCount);
|
u32 Step(u32 instCount);
|
||||||
|
|
||||||
|
// todo: counters for chrono/inst count.
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace riscv
|
} // namespace riscv
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <riscv/Types.hpp>
|
#include <riscv/Types.hpp>
|
||||||
#include <string_view>
|
|
||||||
|
|
||||||
namespace riscv {
|
namespace riscv {
|
||||||
|
|
||||||
|
@ -26,78 +25,41 @@ namespace riscv {
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class Gpr : u8 {
|
enum class Gpr : u8 {
|
||||||
Zero,
|
X0, // zero
|
||||||
Ra,
|
X1,
|
||||||
Sp,
|
X2,
|
||||||
Gp,
|
X3,
|
||||||
Tp,
|
X4,
|
||||||
T0,
|
X5,
|
||||||
T1,
|
X6,
|
||||||
T2,
|
X7,
|
||||||
S0, // also `fp`
|
X8,
|
||||||
S1,
|
X9,
|
||||||
A0,
|
X10,
|
||||||
A1,
|
X11,
|
||||||
A2,
|
X12,
|
||||||
A3,
|
X13,
|
||||||
A4,
|
X14,
|
||||||
A5,
|
X15,
|
||||||
A6,
|
X16,
|
||||||
A7,
|
X17,
|
||||||
S2,
|
X18,
|
||||||
S3,
|
X19,
|
||||||
S4,
|
X20,
|
||||||
S5,
|
X21,
|
||||||
S6,
|
X22,
|
||||||
S7,
|
X23,
|
||||||
S8,
|
X24,
|
||||||
S9,
|
X25,
|
||||||
S10,
|
X26,
|
||||||
S11,
|
X27,
|
||||||
T3,
|
X28,
|
||||||
T4,
|
X29,
|
||||||
T5,
|
X30,
|
||||||
T6
|
X31,
|
||||||
|
PC
|
||||||
};
|
};
|
||||||
|
|
||||||
constexpr std::string_view RegName(Gpr gpr) {
|
|
||||||
std::string_view table[] = {
|
|
||||||
"zero",
|
|
||||||
"ra",
|
|
||||||
"sp",
|
|
||||||
"gp",
|
|
||||||
"tp",
|
|
||||||
"t0",
|
|
||||||
"t1",
|
|
||||||
"t2",
|
|
||||||
"s0",
|
|
||||||
"s1",
|
|
||||||
"a0",
|
|
||||||
"a1",
|
|
||||||
"a2",
|
|
||||||
"a3",
|
|
||||||
"a4",
|
|
||||||
"a5",
|
|
||||||
"a6",
|
|
||||||
"a7",
|
|
||||||
"s2",
|
|
||||||
"s3",
|
|
||||||
"s4",
|
|
||||||
"s5",
|
|
||||||
"s6",
|
|
||||||
"s7",
|
|
||||||
"s8",
|
|
||||||
"s9",
|
|
||||||
"s10",
|
|
||||||
"s11",
|
|
||||||
"t3",
|
|
||||||
"t4",
|
|
||||||
"t5",
|
|
||||||
"t6"
|
|
||||||
};
|
|
||||||
return table[static_cast<usize>(gpr)];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Container for GPRs which can be Statically Typed or not depending on use case.
|
/// Container for GPRs which can be Statically Typed or not depending on use case.
|
||||||
/// Pretty cool, huh?
|
/// Pretty cool, huh?
|
||||||
struct GeneralPurposeRegisters {
|
struct GeneralPurposeRegisters {
|
||||||
|
|
|
@ -14,9 +14,6 @@ namespace riscv::devices {
|
||||||
Address Base() const override;
|
Address Base() const override;
|
||||||
Address Size() const override;
|
Address Size() const override;
|
||||||
|
|
||||||
u8* Raw() const {
|
|
||||||
return memory;
|
|
||||||
}
|
|
||||||
|
|
||||||
u8 PeekByte(Address address) override;
|
u8 PeekByte(Address address) override;
|
||||||
u16 PeekShort(Address address) override;
|
u16 PeekShort(Address address) override;
|
||||||
|
|
|
@ -11,7 +11,6 @@ namespace riscv::devices {
|
||||||
|
|
||||||
SysconDevice(System* system);
|
SysconDevice(System* system);
|
||||||
|
|
||||||
Address Base() const override { return BASE_ADDRESS; }
|
|
||||||
Address Size() const override { return sizeof(u32); } // I think this is right?
|
Address Size() const override { return sizeof(u32); } // I think this is right?
|
||||||
|
|
||||||
u32 Peek(Address address) override;
|
u32 Peek(Address address) override;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
#include <riscv/Bus.hpp>
|
#include <riscv/Bus.hpp>
|
||||||
#include <riscv/CPU.hpp>
|
#include <riscv/CPU.hpp>
|
||||||
#include <riscv/Devices/ClntDevice.hpp>
|
|
||||||
#include <riscv/Devices/RamDevice.hpp>
|
#include <riscv/Devices/RamDevice.hpp>
|
||||||
#include <riscv/Devices/SysconDevice.hpp>
|
#include <riscv/Devices/SysconDevice.hpp>
|
||||||
|
#include <riscv/Devices/ClntDevice.hpp>
|
||||||
|
|
||||||
namespace riscv {
|
namespace riscv {
|
||||||
|
|
||||||
|
@ -10,13 +10,25 @@ namespace riscv {
|
||||||
struct System {
|
struct System {
|
||||||
/// Create a basic system with the basic periphials created.
|
/// Create a basic system with the basic periphials created.
|
||||||
/// All other periphials should be managed by the creator of this System
|
/// All other periphials should be managed by the creator of this System
|
||||||
static System* Create(Address ramSize);
|
static System* WithMemory(Address ramSize);
|
||||||
|
|
||||||
~System();
|
~System();
|
||||||
|
|
||||||
void Step();
|
void AddDeviceToBus(Bus::Device* device);
|
||||||
|
|
||||||
// TODO: callbacks for SYSCON PowerOff and Reboot.
|
/// returns false if the cpu broke execution
|
||||||
|
bool Step();
|
||||||
|
|
||||||
|
CPU* GetCPU();
|
||||||
|
Bus* GetBus();
|
||||||
|
|
||||||
|
private:
|
||||||
|
/// How many Cycle() calls will the bus get
|
||||||
|
/// (also decides ipsRate)
|
||||||
|
u32 cycleRate;
|
||||||
|
|
||||||
|
/// How many instructions will the CPU execute each step
|
||||||
|
u32 ipsRate;
|
||||||
|
|
||||||
Bus* bus;
|
Bus* bus;
|
||||||
|
|
||||||
|
@ -25,9 +37,6 @@ namespace riscv {
|
||||||
devices::RamDevice* ram;
|
devices::RamDevice* ram;
|
||||||
devices::SysconDevice* syscon;
|
devices::SysconDevice* syscon;
|
||||||
devices::ClntDevice* clnt;
|
devices::ClntDevice* clnt;
|
||||||
|
|
||||||
private:
|
|
||||||
System() = default;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace riscv
|
} // namespace riscv
|
||||||
|
|
|
@ -14,8 +14,6 @@ namespace riscv {
|
||||||
if(!device)
|
if(!device)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
device->Attached(this);
|
|
||||||
|
|
||||||
if(device->IsA<CPU*>()) {
|
if(device->IsA<CPU*>()) {
|
||||||
// Return early to avoid putting the CPU pointer inside the devices vector.
|
// Return early to avoid putting the CPU pointer inside the devices vector.
|
||||||
// We do not actually own the CPU.
|
// We do not actually own the CPU.
|
||||||
|
@ -121,7 +119,7 @@ namespace riscv {
|
||||||
}
|
}
|
||||||
|
|
||||||
Bus::Device* Bus::FindDeviceForAddress(Address address) const {
|
Bus::Device* Bus::FindDeviceForAddress(Address address) const {
|
||||||
auto try_find_device = [&](const auto& container, Address address) {
|
auto try_find_device = [&](auto container, Address address) {
|
||||||
return std::find_if(container.begin(), container.end(), [&](const auto& pair) {
|
return std::find_if(container.begin(), container.end(), [&](const auto& pair) {
|
||||||
return
|
return
|
||||||
// We can shorcut region checking if the requested addess matches base address.
|
// We can shorcut region checking if the requested addess matches base address.
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
//! Portions of this code are copyright 2022 Charles Lohr (CNLohr),
|
//! Portions of this code are copyright 2022 Charles Lohr (CNLohr).
|
||||||
//! from [mini-rv32ima](https://github.com/cnlohr/mini-rv32ima).
|
|
||||||
|
|
||||||
#include <riscv/Bus.hpp>
|
#include <riscv/Bus.hpp>
|
||||||
#include <riscv/CPU.hpp>
|
#include <riscv/CPU.hpp>
|
||||||
|
|
||||||
|
#include "riscv/CPUTypes.hpp"
|
||||||
|
|
||||||
namespace riscv {
|
namespace riscv {
|
||||||
|
|
||||||
|
constexpr static Address RamImageOffset = 0x80000000;
|
||||||
|
|
||||||
void CPU::Clock() {
|
void CPU::Clock() {
|
||||||
|
// do the thing
|
||||||
Step(1024);
|
Step(1024);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,38 +36,39 @@ namespace riscv {
|
||||||
// trapCode = 0x80000007;
|
// trapCode = 0x80000007;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::Step(u32 instCount) {
|
u32 CPU::Step(u32 instCount) {
|
||||||
auto interruptsInFlight = [&]() {
|
auto interruptsInFlight = [&]() {
|
||||||
return (mip & (1 << 7) /*|| mip & (1 << 11)*/) && (mie & (1 << 7) /*|| mie & (1 << 11)*/) && (mstatus & 0x8 /*mie*/);
|
return (mip & (1 << 7) /*|| mip & (1 << 11)*/) && (mie & (1 << 7) /*|| mie & (1 << 11)*/) && (mstatus & 0x8 /*mie*/);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Don't run if waiting for an interrupt
|
// Don't run if waiting for an interrupt
|
||||||
if(extraflags & 4)
|
if(extraflags & 4)
|
||||||
return;
|
return 1;
|
||||||
|
|
||||||
u32 rdid = 0;
|
u32 rdid = 0;
|
||||||
u32 rval = 0;
|
u32 rval = 0;
|
||||||
|
u32 pc = this->pc;
|
||||||
u32 cycle = this->cyclel;
|
u32 cycle = this->cyclel;
|
||||||
|
|
||||||
if(interruptsInFlight()) {
|
if(interruptsInFlight()) {
|
||||||
Trap(0x80000007);
|
Trap(0x80000007);
|
||||||
} else {
|
} else {
|
||||||
for(u32 iInst = 0; iInst < instCount; ++iInst) {
|
for(u32 iInst = 0; iInst < instCount; ++iInst) {
|
||||||
rdid = 0; // force it to gpr 0 (zero), which is not writable
|
auto ofs_pc = pc - RamImageOffset;
|
||||||
cycle++;
|
cycle++;
|
||||||
|
|
||||||
if((pc & 3)) {
|
if(ofs_pc & 3) {
|
||||||
Trap(TrapCode::InstructionAddressMisaligned);
|
Trap(TrapCode::InstructionAddressMisaligned);
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
auto ir = bus->PeekWord(pc);
|
auto ir = bus->PeekWord(ofs_pc);
|
||||||
if(trapped) {
|
if(trapped) {
|
||||||
// Overwrite the trap that the bus generated. This might not really work out
|
// Overwrite the trap that the bus generated. This might not really work out
|
||||||
// in practice but should at least kind-of replicate behaviour (our address
|
// in practice but should at least kind-of replicate behaviour (our address
|
||||||
// space is emulated using the [Bus] class, so there is no heap write issue
|
// space is emulated using the [Bus] class, so there is no heap write issue
|
||||||
// that could be caused by leaving it unbound).
|
// that could be caused by leaving it unbound).
|
||||||
Trap(TrapCode::InstructionAccessFault);
|
Trap(TrapCode::InstructionAccessFault);
|
||||||
rval = pc;
|
rval = ofs_pc + RamImageOffset;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,12 +133,12 @@ namespace riscv {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 6: // BLTU
|
case 6: // BLTU
|
||||||
if((u32)rs1 < (u32)rs2)
|
if((uint32_t)rs1 < (uint32_t)rs2)
|
||||||
pc = immm4;
|
pc = immm4;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 7: // BGEU
|
case 7: // BGEU
|
||||||
if((u32)rs1 >= (u32)rs2)
|
if((uint32_t)rs1 >= (uint32_t)rs2)
|
||||||
pc = immm4;
|
pc = immm4;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -148,6 +153,8 @@ namespace riscv {
|
||||||
i32 imm_se = imm | ((imm & 0x800) ? 0xfffff000 : 0);
|
i32 imm_se = imm | ((imm & 0x800) ? 0xfffff000 : 0);
|
||||||
u32 rsval = rs1 + imm_se;
|
u32 rsval = rs1 + imm_se;
|
||||||
|
|
||||||
|
rsval -= RamImageOffset;
|
||||||
|
|
||||||
switch((ir >> 12) & 0x7) {
|
switch((ir >> 12) & 0x7) {
|
||||||
// LB, LH, LW, LBU, LHU
|
// LB, LH, LW, LBU, LHU
|
||||||
case 0:
|
case 0:
|
||||||
|
@ -171,7 +178,7 @@ namespace riscv {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(trapped) {
|
if(trapped) {
|
||||||
rval = rsval; // + RamImageOffset;
|
rval = rsval + RamImageOffset;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -181,7 +188,7 @@ namespace riscv {
|
||||||
u32 addy = ((ir >> 7) & 0x1f) | ((ir & 0xfe000000) >> 20);
|
u32 addy = ((ir >> 7) & 0x1f) | ((ir & 0xfe000000) >> 20);
|
||||||
if(addy & 0x800)
|
if(addy & 0x800)
|
||||||
addy |= 0xfffff000;
|
addy |= 0xfffff000;
|
||||||
addy += rs1;
|
addy += rs1 - RamImageOffset;
|
||||||
rdid = 0;
|
rdid = 0;
|
||||||
|
|
||||||
switch((ir >> 12) & 0x7) {
|
switch((ir >> 12) & 0x7) {
|
||||||
|
@ -193,7 +200,6 @@ namespace riscv {
|
||||||
bus->PokeShort(addy, rs2);
|
bus->PokeShort(addy, rs2);
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
// lucore::LogInfo("storeWord(0x{:08x}, 0x{:08x})", addy, rs2);
|
|
||||||
bus->PokeWord(addy, rs2);
|
bus->PokeWord(addy, rs2);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -201,7 +207,7 @@ namespace riscv {
|
||||||
}
|
}
|
||||||
|
|
||||||
if(trapped) {
|
if(trapped) {
|
||||||
rval = addy; // + RamImageOffset;
|
rval = addy + RamImageOffset;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -411,7 +417,7 @@ namespace riscv {
|
||||||
mstatus |= 8; // Enable interrupts
|
mstatus |= 8; // Enable interrupts
|
||||||
extraflags |= 4; // Set inernal WFI bit
|
extraflags |= 4; // Set inernal WFI bit
|
||||||
this->pc = pc + 4;
|
this->pc = pc + 4;
|
||||||
return;
|
return 1;
|
||||||
} else if(((csrno & 0xff) == 0x02)) { // MRET
|
} else if(((csrno & 0xff) == 0x02)) { // MRET
|
||||||
// https://raw.githubusercontent.com/riscv/virtual-memory/main/specs/663-Svpbmt.pdf
|
// https://raw.githubusercontent.com/riscv/virtual-memory/main/specs/663-Svpbmt.pdf
|
||||||
// Table 7.6. MRET then in mstatus/mstatush sets MPV=0, MPP=0,
|
// Table 7.6. MRET then in mstatus/mstatush sets MPV=0, MPP=0,
|
||||||
|
@ -449,9 +455,15 @@ namespace riscv {
|
||||||
u32 rs2 = gpr[(ir >> 20) & 0x1f];
|
u32 rs2 = gpr[(ir >> 20) & 0x1f];
|
||||||
u32 irmid = (ir >> 27) & 0x1f;
|
u32 irmid = (ir >> 27) & 0x1f;
|
||||||
|
|
||||||
|
// rs1 -= MINIRV32_RAM_IMAGE_OFFSET;
|
||||||
|
|
||||||
|
rs1 -= RamImageOffset;
|
||||||
|
|
||||||
|
// We don't implement load/store from UART or CLNT with RV32A here.
|
||||||
|
|
||||||
rval = bus->PeekWord(rs1);
|
rval = bus->PeekWord(rs1);
|
||||||
if(trapped) {
|
if(trapped) {
|
||||||
rval = rs1; // + RamImageOffset;
|
rval = rs1 + RamImageOffset;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -510,8 +522,10 @@ namespace riscv {
|
||||||
if(trapped)
|
if(trapped)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if(rdid)
|
if(rdid) {
|
||||||
gpr[rdid] = rval;
|
gpr[rdid] = rval;
|
||||||
|
}
|
||||||
|
|
||||||
pc += 4;
|
pc += 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -529,11 +543,13 @@ namespace riscv {
|
||||||
else
|
else
|
||||||
mtval = pc;
|
mtval = pc;
|
||||||
}
|
}
|
||||||
mepc = pc; // Interrupt handler will advance mepc
|
mepc = pc; // TRICKY: The kernel advances mepc automatically.
|
||||||
|
// CSR( mstatus ) & 8 = MIE, & 0x80 = MPIE
|
||||||
|
// On an interrupt, the system moves current MIE into MPIE
|
||||||
mstatus = (mstatus & 0x08) << 4 | ((extraflags)&3) << 11;
|
mstatus = (mstatus & 0x08) << 4 | ((extraflags)&3) << 11;
|
||||||
pc = (mtvec - 4);
|
pc = (mtvec - 4);
|
||||||
|
|
||||||
// Always enter machine mode when trapping.
|
// If trapping, always enter machine mode.
|
||||||
extraflags |= 3;
|
extraflags |= 3;
|
||||||
|
|
||||||
// Reset trap flags
|
// Reset trap flags
|
||||||
|
@ -545,6 +561,8 @@ namespace riscv {
|
||||||
if(cyclel > cycle)
|
if(cyclel > cycle)
|
||||||
cycleh++;
|
cycleh++;
|
||||||
cyclel = cycle;
|
cyclel = cycle;
|
||||||
|
pc = pc;
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace riscv
|
} // namespace riscv
|
||||||
|
|
|
@ -1,39 +0,0 @@
|
||||||
#include <riscv/System.hpp>
|
|
||||||
|
|
||||||
namespace riscv {
|
|
||||||
|
|
||||||
System* System::Create(Address ramSize) {
|
|
||||||
auto* system = new System;
|
|
||||||
|
|
||||||
// create all the devices we require.
|
|
||||||
system->bus = new Bus();
|
|
||||||
system->cpu = new CPU();
|
|
||||||
system->ram = new devices::RamDevice(0x80000000, ramSize);
|
|
||||||
system->clnt = new devices::ClntDevice();
|
|
||||||
system->syscon = new devices::SysconDevice(system);
|
|
||||||
|
|
||||||
// techinically this is done on construction but lets be hard about it
|
|
||||||
system->cpu->Reset();
|
|
||||||
|
|
||||||
// attach everything into the bus
|
|
||||||
if(!system->bus->AttachDevice(system->cpu)) return nullptr;
|
|
||||||
if(!system->bus->AttachDevice(system->clnt)) return nullptr;
|
|
||||||
if(!system->bus->AttachDevice(system->syscon)) return nullptr;
|
|
||||||
if(!system->bus->AttachDevice(system->ram)) return nullptr;
|
|
||||||
|
|
||||||
return system;
|
|
||||||
}
|
|
||||||
|
|
||||||
System::~System() {
|
|
||||||
delete cpu;
|
|
||||||
delete bus; // the rest of the device pointers will be deleted by the bus.
|
|
||||||
}
|
|
||||||
|
|
||||||
void System::Step() {
|
|
||||||
// Clock the bus, it'll do everything else.
|
|
||||||
bus->Clock();
|
|
||||||
|
|
||||||
// Later: handling for invalid cases!
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace riscv
|
|
|
@ -1,10 +1,5 @@
|
||||||
//! A test harness for testing if the riscv library actually works.
|
//! A test harness for testing the riscv library.
|
||||||
#include <riscv/System.hpp>
|
#include <riscv/System.hpp>
|
||||||
#include <lucore/StdoutSink.hpp>
|
|
||||||
|
|
||||||
#include <cstdio> // I know, I know, but this is a test program. Yell later :)
|
|
||||||
#include <lucore/Assert.hpp>
|
|
||||||
#include <lucore/Logger.hpp>
|
|
||||||
|
|
||||||
/// simple 16550 UART implementation
|
/// simple 16550 UART implementation
|
||||||
struct SimpleUartDevice : public riscv::Bus::MmioDevice {
|
struct SimpleUartDevice : public riscv::Bus::MmioDevice {
|
||||||
|
@ -12,56 +7,29 @@ struct SimpleUartDevice : public riscv::Bus::MmioDevice {
|
||||||
|
|
||||||
riscv::Address Base() const override { return BASE_ADDRESS; }
|
riscv::Address Base() const override { return BASE_ADDRESS; }
|
||||||
|
|
||||||
riscv::Address Size() const override { return 12; } // for now
|
riscv::Address Size() const override { return 5; }
|
||||||
|
|
||||||
|
// TODO: emulate properly
|
||||||
u32 Peek(riscv::Address address) override {
|
u32 Peek(riscv::Address address) override {
|
||||||
switch(address) {
|
switch(address) {
|
||||||
case BASE_ADDRESS:
|
case BASE_ADDRESS:
|
||||||
return 0x60; // active, but no keyboard input
|
break;
|
||||||
case BASE_ADDRESS + 5:
|
case BASE_ADDRESS + 5:
|
||||||
return '\0';
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Poke(riscv::Address address, u32 value) override {
|
void Poke(riscv::Address address, u32 value) override {
|
||||||
if(address == BASE_ADDRESS) {
|
if(address == BASE_ADDRESS) { // write to data buffer
|
||||||
char c = value & 0x000000ff;
|
printf("%c\n", value);
|
||||||
//lucore::LogInfo("[UART] got data buffer poke of char {:02x} @ pc 0x{:08x}", c, bus->GetCPU()->pc);
|
|
||||||
//printf("%c", c);
|
|
||||||
fputc(c, stderr);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main() {
|
||||||
lucore::LoggerAttachStdout();
|
auto system = riscv::System::WithMemory(128 * 1024);
|
||||||
|
system->AddDeviceToBus(new SimpleUartDevice);
|
||||||
LUCORE_CHECK(argc == 2, "this test harness expects one argument (the file to load into riscv memory and execute). got {} arguments", argc);
|
|
||||||
|
|
||||||
// 128 KB of ram. Won't be enough to boot linux but should be good enough to test most baremetal apps
|
|
||||||
auto system = riscv::System::Create(128 * 1024);
|
|
||||||
LUCORE_CHECK(system, "could not create system for some reason.");
|
|
||||||
|
|
||||||
// Attach our UART device
|
|
||||||
system->bus->AttachDevice(new SimpleUartDevice);
|
|
||||||
|
|
||||||
auto fp = std::fopen(argv[1], "rb");
|
|
||||||
LUCORE_CHECK(fp, "could not open file \"{}\"", argv[1]);
|
|
||||||
|
|
||||||
fseek(fp, 0, SEEK_END);
|
|
||||||
auto len = ftell(fp);
|
|
||||||
fseek(fp, 0, SEEK_SET);
|
|
||||||
|
|
||||||
fread(system->ram->Raw(), 1, len, fp);
|
|
||||||
fclose(fp);
|
|
||||||
|
|
||||||
// Do the thing now!
|
|
||||||
while(true) {
|
|
||||||
system->Step();
|
|
||||||
}
|
|
||||||
|
|
||||||
delete system;
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
PROJECT = test
|
|
||||||
|
|
||||||
# where your rv32 toolchain is
|
|
||||||
TCPATH = /home/lily/bin/riscv/bin
|
|
||||||
PREFIX = $(TCPATH)/riscv32-unknown-elf
|
|
||||||
|
|
||||||
CC = $(PREFIX)-gcc
|
|
||||||
CXX = $(PREFIX)-g++
|
|
||||||
|
|
||||||
CCFLAGS = -ffreestanding -fno-stack-protector
|
|
||||||
CCFLAGS += -static -static-libgcc -fdata-sections -ffunction-sections
|
|
||||||
CCFLAGS += -g -Os -march=rv32ima -mabi=ilp32
|
|
||||||
|
|
||||||
CXXFLAGS = $(CCFLAGS) -std=c++20 -fno-exceptions -fno-rtti
|
|
||||||
|
|
||||||
LDFLAGS = -T binary.ld -nostdlib -Wl,--gc-sections
|
|
||||||
|
|
||||||
OBJS := start.o main.o
|
|
||||||
|
|
||||||
.PHONY: all test clean
|
|
||||||
|
|
||||||
all: $(PROJECT).bin $(PROJECT).debug.txt
|
|
||||||
|
|
||||||
test: $(PROJECT).bin $(PROJECT).debug.txt
|
|
||||||
../../../../build/projects/riscv_test_harness/rvtest $<
|
|
||||||
|
|
||||||
clean:
|
|
||||||
rm $(PROJECT).elf $(PROJECT).bin $(PROJECT).debug.txt $(OBJS)
|
|
||||||
|
|
||||||
$(PROJECT).elf: $(OBJS)
|
|
||||||
$(CC) $(CCFLAGS) $(LDFLAGS) -o $@ $(OBJS)
|
|
||||||
|
|
||||||
$(PROJECT).bin : $(PROJECT).elf
|
|
||||||
$(PREFIX)-objcopy $^ -O binary $@
|
|
||||||
|
|
||||||
$(PROJECT).debug.txt : $(PROJECT).elf
|
|
||||||
$(PREFIX)-objdump -t $^ > $@
|
|
||||||
$(PREFIX)-objdump -S $^ >> $@
|
|
||||||
|
|
||||||
# Compile rules
|
|
||||||
|
|
||||||
%.o: %.cpp
|
|
||||||
$(CXX) -c $(CXXFLAGS) $< -o $@
|
|
||||||
|
|
||||||
%.o: %.c
|
|
||||||
$(CC) -c $(CCFLAGS) $< -o $@
|
|
||||||
|
|
||||||
%.o: %.S
|
|
||||||
$(CC) -x assembler-with-cpp -march=rv32ima -mabi=ilp32 -c $< -o $@
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
# what
|
|
||||||
|
|
||||||
This is a simple bare-metal riscv program that is able to run on the test harness that does some testing.
|
|
||||||
|
|
||||||
It is barebones, I know.
|
|
|
@ -1,73 +0,0 @@
|
||||||
__heap_size = 0x1000;
|
|
||||||
__stack_size = 0x1000;
|
|
||||||
|
|
||||||
ENTRY(_start)
|
|
||||||
|
|
||||||
SECTIONS
|
|
||||||
{
|
|
||||||
. = 0x80000000;
|
|
||||||
.text : ALIGN(16) {
|
|
||||||
__TEXT_BEGIN__ = .;
|
|
||||||
*(.initial_jump)
|
|
||||||
*(.entry.text)
|
|
||||||
*(.init.literal)
|
|
||||||
*(.init)
|
|
||||||
*(.text)
|
|
||||||
*(.literal .text .literal.* .text.* .stub)
|
|
||||||
*(.out_jump.literal.*)
|
|
||||||
*(.out_jump.*)
|
|
||||||
__TEXT_END__ = .;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If we're on a newer compiler */
|
|
||||||
/DISCARD/ :
|
|
||||||
{
|
|
||||||
*(.interp)
|
|
||||||
*(.dynsym)
|
|
||||||
*(.dynstr)
|
|
||||||
*(.header)
|
|
||||||
} : phdr
|
|
||||||
|
|
||||||
.data : ALIGN(16) {
|
|
||||||
__DATA_BEGIN__ = .;
|
|
||||||
*(.rodata)
|
|
||||||
*(.rodata.*)
|
|
||||||
*(.gnu.linkonce.r.*)
|
|
||||||
*(.rodata1)
|
|
||||||
*(.dynsbss)
|
|
||||||
*(.gnu.linkonce.sb.*)
|
|
||||||
*(.scommon)
|
|
||||||
*(.gnu.linkonce.sb2.*)
|
|
||||||
*(.sbss)
|
|
||||||
*(.sbss.*)
|
|
||||||
*(.sbss2)
|
|
||||||
*(.sbss2.*)
|
|
||||||
*(.dynbss)
|
|
||||||
*(.data)
|
|
||||||
*(.data.*)
|
|
||||||
*(.got)
|
|
||||||
*(.got.*)
|
|
||||||
__DATA_END__ = .;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bss : ALIGN( 16 ) {
|
|
||||||
__BSS_BEGIN__ = .;
|
|
||||||
*(.bss) /* Tricky: BSS needs to be allocated but not sent. GCC Will not populate these for calculating data size */
|
|
||||||
*(.bss.*)
|
|
||||||
__BSS_END__ = .;
|
|
||||||
}
|
|
||||||
|
|
||||||
.heap : ALIGN( 16 ) {
|
|
||||||
_sheap = .;
|
|
||||||
. = . + __heap_size;
|
|
||||||
_eheap = .;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stack : ALIGN( 16 ) {
|
|
||||||
_estack = .;
|
|
||||||
. = . + __stack_size;
|
|
||||||
_sstack = .;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,54 +0,0 @@
|
||||||
// a simple test program
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
uint32_t strlen(const char* str) {
|
|
||||||
if(!str)
|
|
||||||
return 0;
|
|
||||||
const char* c = str;
|
|
||||||
while(*c++)
|
|
||||||
;
|
|
||||||
return c - str;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define UART_BASE 0x10000000
|
|
||||||
#define UART_DATA *(volatile uint32_t*)UART_BASE
|
|
||||||
#define UART_STATUS UART_DATA
|
|
||||||
|
|
||||||
void putc(char c) {
|
|
||||||
UART_DATA = (uint32_t)c;
|
|
||||||
}
|
|
||||||
|
|
||||||
__attribute__((noinline)) void puts(const char* str) {
|
|
||||||
const uint32_t length = strlen(str);
|
|
||||||
for(uint32_t i = 0; i < length; ++i)
|
|
||||||
putc(str[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
static uint32_t value = 0;
|
|
||||||
static uint16_t shortvalue = 0;
|
|
||||||
static uint8_t bytevalue = 0;
|
|
||||||
|
|
||||||
#define COUNTER_TEST(var, max) \
|
|
||||||
for(int i = 0; i < max; ++i) { \
|
|
||||||
puts(#var " is (before modification): "); \
|
|
||||||
putc("0123456789"[var]); \
|
|
||||||
putc('\n'); \
|
|
||||||
\
|
|
||||||
var = i; \
|
|
||||||
puts(#var " is (after modification): "); \
|
|
||||||
putc("0123456789"[var]); \
|
|
||||||
putc('\n'); \
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
puts("hello world I guess\n");
|
|
||||||
|
|
||||||
#if 1
|
|
||||||
COUNTER_TEST(value, 9);
|
|
||||||
COUNTER_TEST(shortvalue, 9);
|
|
||||||
COUNTER_TEST(bytevalue, 9);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// loop forever
|
|
||||||
for(;;);
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
# Simple bare-metal RISCV startup code.
|
|
||||||
|
|
||||||
.section .initial_jump
|
|
||||||
.global _start
|
|
||||||
|
|
||||||
.extern main
|
|
||||||
|
|
||||||
.align 4
|
|
||||||
_start:
|
|
||||||
la sp, _sstack # set up C stack
|
|
||||||
addi sp,sp,-16 # ...
|
|
||||||
sw ra,12(sp) # ...
|
|
||||||
jal ra, main # jump to C code!
|
|
Loading…
Reference in New Issue