Browse Source

Instructions are no longer member functions

confprec
Andrew Waterman 13 years ago
parent
commit
04c2d491c4
  1. 2
      configure
  2. 2
      configure.ac
  3. 35
      riscv/decode.h
  4. 6
      riscv/htif.cc
  5. 4
      riscv/insn_template.cc
  6. 4
      riscv/insns/amoadd_d.h
  7. 4
      riscv/insns/amoadd_w.h
  8. 4
      riscv/insns/amoand_d.h
  9. 4
      riscv/insns/amoand_w.h
  10. 4
      riscv/insns/amomax_d.h
  11. 4
      riscv/insns/amomax_w.h
  12. 4
      riscv/insns/amomaxu_d.h
  13. 4
      riscv/insns/amomaxu_w.h
  14. 4
      riscv/insns/amomin_d.h
  15. 4
      riscv/insns/amomin_w.h
  16. 4
      riscv/insns/amominu_d.h
  17. 4
      riscv/insns/amominu_w.h
  18. 4
      riscv/insns/amoor_d.h
  19. 4
      riscv/insns/amoor_w.h
  20. 4
      riscv/insns/amoswap_d.h
  21. 4
      riscv/insns/amoswap_w.h
  22. 2
      riscv/insns/break.h
  23. 4
      riscv/insns/clearpcr.h
  24. 8
      riscv/insns/eret.h
  25. 2
      riscv/insns/fence_i.h
  26. 2
      riscv/insns/fld.h
  27. 2
      riscv/insns/flw.h
  28. 2
      riscv/insns/frsr.h
  29. 2
      riscv/insns/fsd.h
  30. 4
      riscv/insns/fssr.h
  31. 2
      riscv/insns/fsw.h
  32. 2
      riscv/insns/lb.h
  33. 2
      riscv/insns/lbu.h
  34. 2
      riscv/insns/ld.h
  35. 2
      riscv/insns/lh.h
  36. 2
      riscv/insns/lhu.h
  37. 3
      riscv/insns/lr_d.h
  38. 3
      riscv/insns/lr_w.h
  39. 2
      riscv/insns/lw.h
  40. 2
      riscv/insns/lwu.h
  41. 2
      riscv/insns/mfpcr.h
  42. 4
      riscv/insns/mtpcr.h
  43. 2
      riscv/insns/rdcycle.h
  44. 2
      riscv/insns/rdinstret.h
  45. 2
      riscv/insns/rdtime.h
  46. 2
      riscv/insns/sb.h
  47. 8
      riscv/insns/sc_d.h
  48. 8
      riscv/insns/sc_w.h
  49. 2
      riscv/insns/sd.h
  50. 4
      riscv/insns/setpcr.h
  51. 2
      riscv/insns/sh.h
  52. 2
      riscv/insns/slli.h
  53. 2
      riscv/insns/srai.h
  54. 2
      riscv/insns/srli.h
  55. 2
      riscv/insns/sw.h
  56. 2
      riscv/insns/syscall.h
  57. 29
      riscv/interactive.cc
  58. 17
      riscv/mmu.cc
  59. 27
      riscv/mmu.h
  60. 181
      riscv/processor.cc
  61. 69
      riscv/processor.h
  62. 18
      riscv/sim.cc
  63. 2
      riscv/sim.h
  64. 17
      riscv/trap.cc
  65. 75
      riscv/trap.h

2
configure

@ -4004,7 +4004,7 @@ fi
CFLAGS="-Wall -O2 -Wno-unused"
CXXFLAGS="-Wall -O2 -std=c++0x -Wno-pmf-conversions"
CXXFLAGS="-Wall -O2 -std=c++0x"
#-------------------------------------------------------------------------

2
configure.ac

@ -72,7 +72,7 @@ AC_HEADER_STDC
#-------------------------------------------------------------------------
AC_SUBST([CFLAGS], ["-Wall -O2 -Wno-unused"])
AC_SUBST([CXXFLAGS],["-Wall -O2 -std=c++0x -Wno-pmf-conversions"])
AC_SUBST([CXXFLAGS],["-Wall -O2 -std=c++0x"])
#-------------------------------------------------------------------------
# MCPPBS subproject list

35
riscv/decode.h

@ -168,14 +168,15 @@ private:
};
// helpful macros, etc
#define RS1 XPR[insn.rtype.rs1]
#define RS2 XPR[insn.rtype.rs2]
#define RD XPR.write_port(insn.rtype.rd)
#define RA XPR.write_port(1)
#define FRS1 FPR[insn.ftype.rs1]
#define FRS2 FPR[insn.ftype.rs2]
#define FRS3 FPR[insn.ftype.rs3]
#define FRD FPR.write_port(insn.ftype.rd)
#define MMU (*p->get_mmu())
#define RS1 p->get_state()->XPR[insn.rtype.rs1]
#define RS2 p->get_state()->XPR[insn.rtype.rs2]
#define RD p->get_state()->XPR.write_port(insn.rtype.rd)
#define RA p->get_state()->XPR.write_port(1)
#define FRS1 p->get_state()->FPR[insn.ftype.rs1]
#define FRS2 p->get_state()->FPR[insn.ftype.rs2]
#define FRS3 p->get_state()->FPR[insn.ftype.rs3]
#define FRD p->get_state()->FPR.write_port(insn.ftype.rd)
#define BIGIMM insn.ltype.bigimm
#define SIMM insn.itype.imm12
#define BIMM ((signed)insn.btype.immlo | (insn.btype.immhi << IMMLO_BITS))
@ -187,23 +188,23 @@ private:
#define ITYPE_EADDR sext_xprlen(RS1 + SIMM)
#define BTYPE_EADDR sext_xprlen(RS1 + BIMM)
#define RM ({ int rm = insn.ftype.rm; \
if(rm == 7) rm = (fsr & FSR_RD) >> FSR_RD_SHIFT; \
if(rm > 4) throw trap_illegal_instruction; \
if(rm == 7) rm = (p->get_state()->fsr & FSR_RD) >> FSR_RD_SHIFT; \
if(rm > 4) throw trap_illegal_instruction(); \
rm; })
#define xpr64 (xprlen == 64)
#define require_supervisor if(unlikely(!(sr & SR_S))) throw trap_privileged_instruction
#define require_xpr64 if(unlikely(!xpr64)) throw trap_illegal_instruction
#define require_xpr32 if(unlikely(xpr64)) throw trap_illegal_instruction
#define require_supervisor if(unlikely(!(p->get_state()->sr & SR_S))) throw trap_privileged_instruction()
#define require_xpr64 if(unlikely(!xpr64)) throw trap_illegal_instruction()
#define require_xpr32 if(unlikely(xpr64)) throw trap_illegal_instruction()
#ifndef RISCV_ENABLE_FPU
# define require_fp throw trap_illegal_instruction
# define require_fp throw trap_illegal_instruction()
#else
# define require_fp if(unlikely(!(sr & SR_EF))) throw trap_fp_disabled
# define require_fp if(unlikely(!(p->get_state()->sr & SR_EF))) throw trap_fp_disabled()
#endif
#define cmp_trunc(reg) (reg_t(reg) << (64-xprlen))
#define set_fp_exceptions ({ set_fsr(fsr | \
#define set_fp_exceptions ({ p->set_fsr(p->get_state()->fsr | \
(softfloat_exceptionFlags << FSR_AEXC_SHIFT)); \
softfloat_exceptionFlags = 0; })
@ -220,7 +221,7 @@ private:
#define set_pc(x) \
do { if ((x) & 3 /* For now... */) \
throw trap_instruction_address_misaligned; \
throw trap_instruction_address_misaligned(); \
npc = (x); \
} while(0)

6
riscv/htif.cc

@ -41,7 +41,7 @@ void htif_isasim_t::tick_once()
uint64_t buf[hdr.data_size];
for (size_t i = 0; i < hdr.data_size; i++)
buf[i] = sim->mmu->load_uint64((hdr.addr+i)*HTIF_DATA_ALIGN);
buf[i] = sim->debug_mmu->load_uint64((hdr.addr+i)*HTIF_DATA_ALIGN);
send(buf, hdr.data_size * sizeof(buf[0]));
break;
}
@ -49,7 +49,7 @@ void htif_isasim_t::tick_once()
{
const uint64_t* buf = (const uint64_t*)p.get_payload();
for (size_t i = 0; i < hdr.data_size; i++)
sim->mmu->store_uint64((hdr.addr+i)*HTIF_DATA_ALIGN, buf[i]);
sim->debug_mmu->store_uint64((hdr.addr+i)*HTIF_DATA_ALIGN, buf[i]);
packet_header_t ack(HTIF_CMD_ACK, seqno, 0, 0);
send(&ack, sizeof(ack));
@ -77,7 +77,7 @@ void htif_isasim_t::tick_once()
send(&old_val, sizeof(old_val));
if (regno == PCR_TOHOST)
sim->procs[coreid]->tohost = 0;
sim->procs[coreid]->state.tohost = 0;
if (hdr.cmd == HTIF_CMD_WRITE_CONTROL_REG)
{

4
riscv/insn_template.cc

@ -8,7 +8,7 @@
#include "internals.h" // ditto
#include <assert.h>
reg_t processor_t::rv32_NAME(insn_t insn, reg_t pc)
reg_t rv32_NAME(processor_t* p, insn_t insn, reg_t pc)
{
int xprlen = 32;
reg_t npc = sext_xprlen(pc + insn_length(OPCODE));
@ -16,7 +16,7 @@ reg_t processor_t::rv32_NAME(insn_t insn, reg_t pc)
return npc;
}
reg_t processor_t::rv64_NAME(insn_t insn, reg_t pc)
reg_t rv64_NAME(processor_t* p, insn_t insn, reg_t pc)
{
int xprlen = 64;
reg_t npc = sext_xprlen(pc + insn_length(OPCODE));

4
riscv/insns/amoadd_d.h

@ -1,4 +1,4 @@
require_xpr64;
reg_t v = mmu.load_uint64(RS1);
mmu.store_uint64(RS1, RS2 + v);
reg_t v = MMU.load_uint64(RS1);
MMU.store_uint64(RS1, RS2 + v);
RD = v;

4
riscv/insns/amoadd_w.h

@ -1,3 +1,3 @@
reg_t v = mmu.load_int32(RS1);
mmu.store_uint32(RS1, RS2 + v);
reg_t v = MMU.load_int32(RS1);
MMU.store_uint32(RS1, RS2 + v);
RD = v;

4
riscv/insns/amoand_d.h

@ -1,4 +1,4 @@
require_xpr64;
reg_t v = mmu.load_uint64(RS1);
mmu.store_uint64(RS1, RS2 & v);
reg_t v = MMU.load_uint64(RS1);
MMU.store_uint64(RS1, RS2 & v);
RD = v;

4
riscv/insns/amoand_w.h

@ -1,3 +1,3 @@
reg_t v = mmu.load_int32(RS1);
mmu.store_uint32(RS1, RS2 & v);
reg_t v = MMU.load_int32(RS1);
MMU.store_uint32(RS1, RS2 & v);
RD = v;

4
riscv/insns/amomax_d.h

@ -1,4 +1,4 @@
require_xpr64;
sreg_t v = mmu.load_int64(RS1);
mmu.store_uint64(RS1, std::max(sreg_t(RS2),v));
sreg_t v = MMU.load_int64(RS1);
MMU.store_uint64(RS1, std::max(sreg_t(RS2),v));
RD = v;

4
riscv/insns/amomax_w.h

@ -1,3 +1,3 @@
int32_t v = mmu.load_int32(RS1);
mmu.store_uint32(RS1, std::max(int32_t(RS2),v));
int32_t v = MMU.load_int32(RS1);
MMU.store_uint32(RS1, std::max(int32_t(RS2),v));
RD = v;

4
riscv/insns/amomaxu_d.h

@ -1,4 +1,4 @@
require_xpr64;
reg_t v = mmu.load_uint64(RS1);
mmu.store_uint64(RS1, std::max(RS2,v));
reg_t v = MMU.load_uint64(RS1);
MMU.store_uint64(RS1, std::max(RS2,v));
RD = v;

4
riscv/insns/amomaxu_w.h

@ -1,3 +1,3 @@
uint32_t v = mmu.load_int32(RS1);
mmu.store_uint32(RS1, std::max(uint32_t(RS2),v));
uint32_t v = MMU.load_int32(RS1);
MMU.store_uint32(RS1, std::max(uint32_t(RS2),v));
RD = (int32_t)v;

4
riscv/insns/amomin_d.h

@ -1,4 +1,4 @@
require_xpr64;
sreg_t v = mmu.load_int64(RS1);
mmu.store_uint64(RS1, std::min(sreg_t(RS2),v));
sreg_t v = MMU.load_int64(RS1);
MMU.store_uint64(RS1, std::min(sreg_t(RS2),v));
RD = v;

4
riscv/insns/amomin_w.h

@ -1,3 +1,3 @@
int32_t v = mmu.load_int32(RS1);
mmu.store_uint32(RS1, std::min(int32_t(RS2),v));
int32_t v = MMU.load_int32(RS1);
MMU.store_uint32(RS1, std::min(int32_t(RS2),v));
RD = v;

4
riscv/insns/amominu_d.h

@ -1,4 +1,4 @@
require_xpr64;
reg_t v = mmu.load_uint64(RS1);
mmu.store_uint64(RS1, std::min(RS2,v));
reg_t v = MMU.load_uint64(RS1);
MMU.store_uint64(RS1, std::min(RS2,v));
RD = v;

4
riscv/insns/amominu_w.h

@ -1,3 +1,3 @@
uint32_t v = mmu.load_int32(RS1);
mmu.store_uint32(RS1, std::min(uint32_t(RS2),v));
uint32_t v = MMU.load_int32(RS1);
MMU.store_uint32(RS1, std::min(uint32_t(RS2),v));
RD = (int32_t)v;

4
riscv/insns/amoor_d.h

@ -1,4 +1,4 @@
require_xpr64;
reg_t v = mmu.load_uint64(RS1);
mmu.store_uint64(RS1, RS2 | v);
reg_t v = MMU.load_uint64(RS1);
MMU.store_uint64(RS1, RS2 | v);
RD = v;

4
riscv/insns/amoor_w.h

@ -1,3 +1,3 @@
reg_t v = mmu.load_int32(RS1);
mmu.store_uint32(RS1, RS2 | v);
reg_t v = MMU.load_int32(RS1);
MMU.store_uint32(RS1, RS2 | v);
RD = v;

4
riscv/insns/amoswap_d.h

@ -1,4 +1,4 @@
require_xpr64;
reg_t v = mmu.load_uint64(RS1);
mmu.store_uint64(RS1, RS2);
reg_t v = MMU.load_uint64(RS1);
MMU.store_uint64(RS1, RS2);
RD = v;

4
riscv/insns/amoswap_w.h

@ -1,3 +1,3 @@
reg_t v = mmu.load_int32(RS1);
mmu.store_uint32(RS1, RS2);
reg_t v = MMU.load_int32(RS1);
MMU.store_uint32(RS1, RS2);
RD = v;

2
riscv/insns/break.h

@ -1 +1 @@
throw trap_breakpoint;
throw trap_breakpoint();

4
riscv/insns/clearpcr.h

@ -1,4 +1,2 @@
require_supervisor;
reg_t temp = get_pcr(insn.rtype.rs1);
set_pcr(insn.rtype.rs1, temp & ~SIMM);
RD = temp;
RD = p->set_pcr(insn.rtype.rs1, p->get_pcr(insn.rtype.rs1) & ~SIMM);

8
riscv/insns/eret.h

@ -1,5 +1,5 @@
require_supervisor;
set_pcr(PCR_SR, ((sr & ~(SR_S | SR_EI)) |
((sr & SR_PS) ? SR_S : 0)) |
((sr & SR_PEI) ? SR_EI : 0));
set_pc(epc);
p->set_pcr(PCR_SR, ((p->get_state()->sr & ~(SR_S | SR_EI)) |
((p->get_state()->sr & SR_PS) ? SR_S : 0)) |
((p->get_state()->sr & SR_PEI) ? SR_EI : 0));
set_pc(p->get_state()->epc);

2
riscv/insns/fence_i.h

@ -1 +1 @@
mmu.flush_icache();
MMU.flush_icache();

2
riscv/insns/fld.h

@ -1,2 +1,2 @@
require_fp;
FRD = mmu.load_int64(ITYPE_EADDR);
FRD = MMU.load_int64(ITYPE_EADDR);

2
riscv/insns/flw.h

@ -1,2 +1,2 @@
require_fp;
FRD = mmu.load_int32(ITYPE_EADDR);
FRD = MMU.load_int32(ITYPE_EADDR);

2
riscv/insns/frsr.h

@ -1,2 +1,2 @@
require_fp;
RD = fsr;
RD = p->get_fsr();

2
riscv/insns/fsd.h

@ -1,2 +1,2 @@
require_fp;
mmu.store_uint64(BTYPE_EADDR, FRS2);
MMU.store_uint64(BTYPE_EADDR, FRS2);

4
riscv/insns/fssr.h

@ -1,4 +1,2 @@
require_fp;
uint32_t tmp = fsr;
set_fsr(RS1);
RD = tmp;
RD = p->set_fsr(RS1);

2
riscv/insns/fsw.h

@ -1,2 +1,2 @@
require_fp;
mmu.store_uint32(BTYPE_EADDR, FRS2);
MMU.store_uint32(BTYPE_EADDR, FRS2);

2
riscv/insns/lb.h

@ -1 +1 @@
RD = mmu.load_int8(ITYPE_EADDR);
RD = MMU.load_int8(ITYPE_EADDR);

2
riscv/insns/lbu.h

@ -1 +1 @@
RD = mmu.load_uint8(ITYPE_EADDR);
RD = MMU.load_uint8(ITYPE_EADDR);

2
riscv/insns/ld.h

@ -1,2 +1,2 @@
require_xpr64;
RD = mmu.load_int64(ITYPE_EADDR);
RD = MMU.load_int64(ITYPE_EADDR);

2
riscv/insns/lh.h

@ -1 +1 @@
RD = mmu.load_int16(ITYPE_EADDR);
RD = MMU.load_int16(ITYPE_EADDR);

2
riscv/insns/lhu.h

@ -1 +1 @@
RD = mmu.load_uint16(ITYPE_EADDR);
RD = MMU.load_uint16(ITYPE_EADDR);

3
riscv/insns/lr_d.h

@ -1,2 +1,3 @@
require_xpr64;
RD = mmu.load_reserved_int64(RS1);
p->get_state()->load_reservation = RS1;
RD = MMU.load_int64(RS1);

3
riscv/insns/lr_w.h

@ -1 +1,2 @@
RD = mmu.load_reserved_int32(RS1);
p->get_state()->load_reservation = RS1;
RD = MMU.load_int32(RS1);

2
riscv/insns/lw.h

@ -1 +1 @@
RD = mmu.load_int32(ITYPE_EADDR);
RD = MMU.load_int32(ITYPE_EADDR);

2
riscv/insns/lwu.h

@ -1,2 +1,2 @@
require_xpr64;
RD = mmu.load_uint32(ITYPE_EADDR);
RD = MMU.load_uint32(ITYPE_EADDR);

2
riscv/insns/mfpcr.h

@ -1,2 +1,2 @@
require_supervisor;
RD = get_pcr(insn.rtype.rs1);
RD = p->get_pcr(insn.rtype.rs1);

4
riscv/insns/mtpcr.h

@ -1,4 +1,2 @@
require_supervisor;
reg_t val = get_pcr(insn.rtype.rs1);
set_pcr(insn.rtype.rs1, RS2);
RD = val;
RD = p->set_pcr(insn.rtype.rs1, RS2);

2
riscv/insns/rdcycle.h

@ -1 +1 @@
RD = cycle;
RD = sext_xprlen(p->get_state()->cycle);

2
riscv/insns/rdinstret.h

@ -1 +1 @@
RD = cycle;
#include "insns/rdcycle.h"

2
riscv/insns/rdtime.h

@ -1 +1 @@
RD = cycle;
#include "insns/rdcycle.h"

2
riscv/insns/sb.h

@ -1 +1 @@
mmu.store_uint8(BTYPE_EADDR, RS2);
MMU.store_uint8(BTYPE_EADDR, RS2);

8
riscv/insns/sc_d.h

@ -1,2 +1,8 @@
require_xpr64;
RD = mmu.store_conditional_uint64(RS1, RS2);
if (RS1 == p->get_state()->load_reservation)
{
MMU.store_uint64(RS1, RS2);
RD = 0;
}
else
RD = 1;

8
riscv/insns/sc_w.h

@ -1 +1,7 @@
RD = mmu.store_conditional_uint32(RS1, RS2);
if (RS1 == p->get_state()->load_reservation)
{
MMU.store_uint32(RS1, RS2);
RD = 0;
}
else
RD = 1;

2
riscv/insns/sd.h

@ -1,2 +1,2 @@
require_xpr64;
mmu.store_uint64(BTYPE_EADDR, RS2);
MMU.store_uint64(BTYPE_EADDR, RS2);

4
riscv/insns/setpcr.h

@ -1,4 +1,2 @@
require_supervisor;
reg_t temp = get_pcr(insn.rtype.rs1);
set_pcr(insn.rtype.rs1, temp | SIMM);
RD = temp;
RD = p->set_pcr(insn.rtype.rs1, p->get_pcr(insn.rtype.rs1) | SIMM);

2
riscv/insns/sh.h

@ -1 +1 @@
mmu.store_uint16(BTYPE_EADDR, RS2);
MMU.store_uint16(BTYPE_EADDR, RS2);

2
riscv/insns/slli.h

@ -3,6 +3,6 @@ if(xpr64)
else
{
if(SHAMT & 0x20)
throw trap_illegal_instruction;
throw trap_illegal_instruction();
RD = sext32(RS1 << SHAMT);
}

2
riscv/insns/srai.h

@ -3,6 +3,6 @@ if(xpr64)
else
{
if(SHAMT & 0x20)
throw trap_illegal_instruction;
throw trap_illegal_instruction();
RD = sext32(int32_t(RS1) >> SHAMT);
}

2
riscv/insns/srli.h

@ -3,6 +3,6 @@ if(xpr64)
else
{
if(SHAMT & 0x20)
throw trap_illegal_instruction;
throw trap_illegal_instruction();
RD = sext32((uint32_t)RS1 >> SHAMT);
}

2
riscv/insns/sw.h

@ -1 +1 @@
mmu.store_uint32(BTYPE_EADDR, RS2);
MMU.store_uint32(BTYPE_EADDR, RS2);

2
riscv/insns/syscall.h

@ -1 +1 @@
throw trap_syscall;
throw trap_syscall();

29
riscv/interactive.cc

@ -113,39 +113,39 @@ void sim_t::interactive_quit(const std::string& cmd, const std::vector<std::stri
reg_t sim_t::get_pc(const std::vector<std::string>& args)
{
if(args.size() != 1)
throw trap_illegal_instruction;
throw trap_illegal_instruction();
int p = atoi(args[0].c_str());
if(p >= (int)num_cores())
throw trap_illegal_instruction;
throw trap_illegal_instruction();
return procs[p]->pc;
return procs[p]->state.pc;
}
reg_t sim_t::get_reg(const std::vector<std::string>& args)
{
if(args.size() != 2)
throw trap_illegal_instruction;
throw trap_illegal_instruction();
int p = atoi(args[0].c_str());
int r = atoi(args[1].c_str());
if(p >= (int)num_cores() || r >= NXPR)
throw trap_illegal_instruction;
throw trap_illegal_instruction();
return procs[p]->XPR[r];
return procs[p]->state.XPR[r];
}
reg_t sim_t::get_freg(const std::vector<std::string>& args)
{
if(args.size() != 2)
throw trap_illegal_instruction;
throw trap_illegal_instruction();
int p = atoi(args[0].c_str());
int r = atoi(args[1].c_str());
if(p >= (int)num_cores() || r >= NFPR)
throw trap_illegal_instruction;
throw trap_illegal_instruction();
return procs[p]->FPR[r];
return procs[p]->state.FPR[r];
}
void sim_t::interactive_reg(const std::string& cmd, const std::vector<std::string>& args)
@ -177,15 +177,16 @@ void sim_t::interactive_fregd(const std::string& cmd, const std::vector<std::str
reg_t sim_t::get_mem(const std::vector<std::string>& args)
{
if(args.size() != 1 && args.size() != 2)
throw trap_illegal_instruction;
throw trap_illegal_instruction();
std::string addr_str = args[0];
mmu_t* mmu = debug_mmu;
if(args.size() == 2)
{
int p = atoi(args[0].c_str());
if(p >= (int)num_cores())
throw trap_illegal_instruction;
mmu->set_ptbr(procs[p]->mmu.get_ptbr());
throw trap_illegal_instruction();
mmu = procs[p]->get_mmu();
addr_str = args[1];
}
@ -220,12 +221,12 @@ void sim_t::interactive_mem(const std::string& cmd, const std::vector<std::strin
void sim_t::interactive_str(const std::string& cmd, const std::vector<std::string>& args)
{
if(args.size() != 1)
throw trap_illegal_instruction;
throw trap_illegal_instruction();
reg_t addr = strtol(args[0].c_str(),NULL,16);
char ch;
while((ch = mmu->load_uint8(addr++)))
while((ch = debug_mmu->load_uint8(addr++)))
putchar(ch);
putchar('\n');

17
riscv/mmu.cc

@ -5,8 +5,7 @@
#include "processor.h"
mmu_t::mmu_t(char* _mem, size_t _memsz)
: mem(_mem), memsz(_memsz), badvaddr(0),
ptbr(0), proc(NULL)
: mem(_mem), memsz(_memsz), proc(NULL)
{
flush_tlb();
}
@ -27,7 +26,6 @@ void mmu_t::flush_tlb()
memset(tlb_store_tag, -1, sizeof(tlb_store_tag));
flush_icache();
yield_load_reservation();
}
reg_t mmu_t::refill_tlb(reg_t addr, reg_t bytes, bool store, bool fetch)
@ -38,7 +36,7 @@ reg_t mmu_t::refill_tlb(reg_t addr, reg_t bytes, bool store, bool fetch)
reg_t pte = walk(addr);
reg_t pte_perm = pte & PTE_PERM;
if (proc == NULL || (proc->sr & SR_S))
if (proc == NULL || (proc->state.sr & SR_S))
pte_perm = (pte_perm/(PTE_SX/PTE_UX)) & PTE_PERM;
pte_perm |= pte & PTE_V;
@ -46,10 +44,11 @@ reg_t mmu_t::refill_tlb(reg_t addr, reg_t bytes, bool store, bool fetch)
if(unlikely((pte_perm & perm) != perm))
{
if (fetch)
throw trap_instruction_access_fault;
throw trap_instruction_access_fault();
badvaddr = addr;
throw store ? trap_store_access_fault : trap_load_access_fault;
if (store)
throw trap_store_access_fault(addr);
throw trap_load_access_fault(addr);
}
reg_t pgoff = addr & (PGSIZE-1);
@ -77,14 +76,14 @@ pte_t mmu_t::walk(reg_t addr)
int shift = 8*sizeof(reg_t) - VA_BITS;
if (((sreg_t)addr << shift >> shift) != (sreg_t)addr)
;
else if (proc == NULL || !(proc->sr & SR_VM))
else if (proc == NULL || !(proc->state.sr & SR_VM))
{
if(addr < memsz)
pte = PTE_V | PTE_PERM | ((addr >> PGSHIFT) << PGSHIFT);
}
else
{
reg_t base = ptbr;
reg_t base = proc->get_state()->ptbr;
reg_t ptd;
int ptshift = (LEVELS-1)*PTIDXBITS;

27
riscv/mmu.h

@ -33,16 +33,9 @@ public:
#define load_func(type) \
type##_t load_##type(reg_t addr) { \
if(unlikely(addr % sizeof(type##_t))) \
{ \
badvaddr = addr; \
throw trap_load_address_misaligned; \
} \
throw trap_load_address_misaligned(addr); \
reg_t paddr = translate(addr, sizeof(type##_t), false, false); \
return *(type##_t*)(mem + paddr); \
} \
type##_t load_reserved_##type(reg_t addr) { \
load_reservation = addr; \
return load_##type(addr); \
}
// load value from memory at aligned address; zero extend to register width
@ -61,18 +54,9 @@ public:
#define store_func(type) \
void store_##type(reg_t addr, type##_t val) { \
if(unlikely(addr % sizeof(type##_t))) \
{ \
badvaddr = addr; \
throw trap_store_address_misaligned; \
} \
throw trap_store_address_misaligned(addr); \
reg_t paddr = translate(addr, sizeof(type##_t), true, false); \
*(type##_t*)(mem + paddr) = val; \
} \
reg_t store_conditional_##type(reg_t addr, type##_t val) { \
if (addr == load_reservation) { \
store_##type(addr, val); \
return 0; \
} else return 1; \
}
// store value to memory at aligned address
@ -111,23 +95,16 @@ public:
return icache_data[idx];
}
reg_t get_badvaddr() { return badvaddr; }
reg_t get_ptbr() { return ptbr; }
void set_ptbr(reg_t addr) { ptbr = addr & ~(PGSIZE-1); }
void set_processor(processor_t* p) { proc = p; flush_tlb(); }
void flush_tlb();
void flush_icache();
void yield_load_reservation() { load_reservation = -1; }
void register_memtracer(memtracer_t*);
private:
char* mem;
size_t memsz;
reg_t load_reservation;
reg_t badvaddr;
reg_t ptbr;
processor_t* proc;
memtracer_list_t tracer;

181
riscv/processor.cc

@ -19,7 +19,9 @@ processor_t::processor_t(sim_t* _sim, mmu_t* _mmu, uint32_t _id)
mmu.set_processor(this);
#define DECLARE_INSN(name, match, mask) \
register_insn(match, mask, (insn_func_t)&processor_t::rv32_##name, (insn_func_t)&processor_t::rv64_##name);
extern reg_t rv32_##name(processor_t*, insn_t, reg_t); \
extern reg_t rv64_##name(processor_t*, insn_t, reg_t); \
register_insn(match, mask, rv32_##name, rv64_##name);
#include "opcodes.h"
#undef DECLARE_INSN
}
@ -28,17 +30,15 @@ processor_t::~processor_t()
{
}
void processor_t::reset(bool value)
void state_t::reset()
{
if (run == !value)
return;
run = !value;
// the ISA guarantees on boot that the PC is 0x2000 and the the processor
// is in supervisor mode, and in 64-bit mode, if supported, with traps
// and virtual memory disabled.
sr = 0;
set_pcr(PCR_SR, SR_S | SR_S64 | SR_IM);
sr = SR_S;
#ifdef RISCV_ENABLE_64BIT
sr |= SR_S64;
#endif
pc = 0x2000;
// the following state is undefined upon boot-up,
@ -55,23 +55,36 @@ void processor_t::reset(bool value)
count = 0;
compare = 0;
cycle = 0;
set_fsr(0);
fsr = 0;
load_reservation = -1;
}
void processor_t::reset(bool value)
{
if (run == !value)
return;
run = !value;
state.reset();
}
void processor_t::set_fsr(uint32_t val)
uint32_t processor_t::set_fsr(uint32_t val)
{
fsr = val & ~FSR_ZERO; // clear FSR bits that read as zero
uint32_t old_fsr = state.fsr;
state.fsr = val & ~FSR_ZERO; // clear FSR bits that read as zero
return old_fsr;
}
void processor_t::take_interrupt()
{
uint32_t interrupts = (sr & SR_IP) >> SR_IP_SHIFT;
interrupts &= (sr & SR_IM) >> SR_IM_SHIFT;
uint32_t interrupts = (state.sr & SR_IP) >> SR_IP_SHIFT;
interrupts &= (state.sr & SR_IM) >> SR_IM_SHIFT;
if(interrupts && (sr & SR_EI))
for(int i = 0; ; i++, interrupts >>= 1)
if(interrupts & 1)
throw interrupt_t(i);
if (interrupts && (state.sr & SR_EI))
for (int i = 0; ; i++, interrupts >>= 1)
if (interrupts & 1)
throw trap_t((1ULL << ((state.sr & SR_S64) ? 63 : 31)) + i);
}
void processor_t::step(size_t n, bool noisy)
@ -80,20 +93,19 @@ void processor_t::step(size_t n, bool noisy)
return;
size_t i = 0;
reg_t npc = state.pc;
mmu_t& _mmu = mmu;
try
{
take_interrupt();
mmu_t& _mmu = mmu;
reg_t npc = pc;
// execute_insn fetches and executes one instruction
#define execute_insn(noisy) \
do { \
mmu_t::insn_fetch_t fetch = _mmu.load_insn(npc); \
if(noisy) disasm(fetch.insn, npc); \
npc = fetch.func(this, fetch.insn, npc); \
pc = npc; \
} while(0)
if(noisy) for( ; i < n; i++) // print out instructions as we go
@ -111,46 +123,45 @@ void processor_t::step(size_t n, bool noisy)
for( ; i < n; i++)
execute_insn(false);
}
state.pc = npc;
}
catch(trap_t t)
{
// an exception occurred in the target processor
take_trap(t,noisy);
}
catch(interrupt_t t)
catch(trap_t& t)
{
take_trap((1ULL << ((sr & SR_S64) ? 63 : 31)) + t.i, noisy);
take_trap(npc, t, noisy);
}
cycle += i;
state.cycle += i;
// update timer and possibly register a timer interrupt
uint32_t old_count = count;
count += i;
if(old_count < compare && uint64_t(old_count) + i >= compare)
uint32_t old_count = state.count;
state.count += i;
if(old_count < state.compare && uint64_t(old_count) + i >= state.compare)
set_interrupt(IRQ_TIMER, true);
}
void processor_t::take_trap(reg_t t, bool noisy)
void processor_t::take_trap(reg_t pc, trap_t& t, bool noisy)
{
if(noisy)
{
if ((sreg_t)t < 0)
if ((sreg_t)t.cause() < 0)
fprintf(stderr, "core %3d: interrupt %d, epc 0x%016" PRIx64 "\n",
id, uint8_t(t), pc);
id, uint8_t(t.cause()), pc);
else
fprintf(stderr, "core %3d: trap %s, epc 0x%016" PRIx64 "\n",
id, trap_name(trap_t(t)), pc);
id, t.name(), pc);
}
// switch to supervisor, set previous supervisor bit, disable traps
set_pcr(PCR_SR, (((sr & ~SR_EI) | SR_S) & ~SR_PS & ~SR_PEI) |
((sr & SR_S) ? SR_PS : 0) |
((sr & SR_EI) ? SR_PEI : 0));
cause = t;
epc = pc;
pc = evec;
badvaddr = mmu.get_badvaddr();
// switch to supervisor, set previous supervisor bit, disable interrupts
set_pcr(PCR_SR, (((state.sr & ~SR_EI) | SR_S) & ~SR_PS & ~SR_PEI) |
((state.sr & SR_S) ? SR_PS : 0) |
((state.sr & SR_EI) ? SR_PEI : 0));
yield_load_reservation();
state.cause = t.cause();
state.epc = pc;
state.pc = state.evec;
t.side_effects(&state); // might set badvaddr etc.
}
void processor_t::deliver_ipi()
@ -164,42 +175,44 @@ void processor_t::disasm(insn_t insn, reg_t pc)
// the disassembler is stateless, so we share it
static disassembler disasm;
fprintf(stderr, "core %3d: 0x%016" PRIx64 " (0x%08" PRIxFAST32 ") %s\n",
id, pc, insn.bits, disasm.disassemble(insn).c_str());
id, state.pc, insn.bits, disasm.disassemble(insn).c_str());
}
void processor_t::set_pcr(int which, reg_t val)
reg_t processor_t::set_pcr(int which, reg_t val)
{
reg_t old_pcr = get_pcr(which);
switch (which)
{
case PCR_SR:
sr = (val & ~SR_IP) | (sr & SR_IP);
state.sr = (val & ~SR_IP) | (state.sr & SR_IP);
#ifndef RISCV_ENABLE_64BIT
sr &= ~(SR_S64 | SR_U64);
state.sr &= ~(SR_S64 | SR_U64);
#endif
#ifndef RISCV_ENABLE_FPU
sr &= ~SR_EF;
state.sr &= ~SR_EF;
#endif
#ifndef RISCV_ENABLE_VEC
sr &= ~SR_EV;
state.sr &= ~SR_EV;
#endif
sr &= ~SR_ZERO;
state.sr &= ~SR_ZERO;
mmu.flush_tlb();
break;
case PCR_EPC:
epc = val;
state.epc = val;
break;
case PCR_EVEC:
evec = val;
state.evec = val;
break;
case PCR_COUNT:
count = val;
state.count = val;
break;
case PCR_COMPARE:
set_interrupt(IRQ_TIMER, false);
compare = val;
state.compare = val;
break;
case PCR_PTBR:
mmu.set_ptbr(val);
state.ptbr = val & ~(PGSIZE-1);
break;
case PCR_SEND_IPI:
sim.send_ipi(val);
@ -208,20 +221,22 @@ void processor_t::set_pcr(int which, reg_t val)
set_interrupt(IRQ_IPI, val & 1);
break;
case PCR_K0:
pcr_k0 = val;
state.pcr_k0 = val;
break;
case PCR_K1:
pcr_k1 = val;
state.pcr_k1 = val;
break;
case PCR_TOHOST:
if (tohost == 0)
tohost = val;
if (state.tohost == 0)
state.tohost = val;
break;
case PCR_FROMHOST:
set_interrupt(IRQ_HOST, val != 0);
fromhost = val;
state.fromhost = val;
break;
}
return old_pcr;
}
reg_t processor_t::get_pcr(int which)
@ -229,37 +244,38 @@ reg_t processor_t::get_pcr(int which)
switch (which)
{
case PCR_SR:
return sr;
return state.sr;
case PCR_EPC:
return epc;
return state.epc;
case PCR_BADVADDR:
return badvaddr;
return state.badvaddr;
case PCR_EVEC:
return evec;
return state.evec;
case PCR_COUNT:
return count;
return state.count;
case PCR_COMPARE:
return compare;
return state.compare;
case PCR_CAUSE:
return cause;
return state.cause;
case PCR_PTBR:
return mmu.get_ptbr();
return state.ptbr;
case PCR_ASID:
return 0;
case PCR_FATC:
mmu.flush_tlb();
return 0;
case PCR_HARTID:
return id;
case PCR_IMPL:
return 1;
case PCR_K0:
return pcr_k0;
return state.pcr_k0;
case PCR_K1:
return pcr_k1;
return state.pcr_k1;
case PCR_TOHOST:
return tohost;
return state.tohost;
case PCR_FROMHOST:
return fromhost;
return state.fromhost;
}
return -1;
}
@ -268,27 +284,26 @@ void processor_t::set_interrupt(int which, bool on)
{
uint32_t mask = (1 << (which + SR_IP_SHIFT)) & SR_IP;
if (on)
sr |= mask;
state.sr |= mask;
else
sr &= ~mask;
state.sr &= ~mask;
}
static reg_t illegal_instruction(processor_t* p, insn_t insn, reg_t pc)
{
throw trap_illegal_instruction();
}
insn_func_t processor_t::decode_insn(insn_t insn)
{
bool rv64 = (sr & SR_S) ? (sr & SR_S64) : (sr & SR_U64);
bool rv64 = (state.sr & SR_S) ? (state.sr & SR_S64) : (state.sr & SR_U64);
auto key = insn.bits & ((1L << opcode_bits)-1);
auto it = opcode_map.find(key);
for (auto it = opcode_map.find(key); it != opcode_map.end() && it->first == key; ++it)
if ((insn.bits & it->second.mask) == it->second.match)
return rv64 ? it->second.rv64 : it->second.rv32;
return &processor_t::illegal_instruction;
}
reg_t processor_t::illegal_instruction(insn_t insn, reg_t pc)
{
throw trap_illegal_instruction;
return &illegal_instruction;
}
void processor_t::register_insn(uint32_t match, uint32_t mask, insn_func_t rv32, insn_func_t rv64)

69
riscv/processor.h

@ -5,7 +5,6 @@
#include "decode.h"
#include <cstring>
#include "trap.h"
#include "config.h"
#include <map>
@ -13,30 +12,14 @@ class processor_t;
class mmu_t;
typedef reg_t (*insn_func_t)(processor_t*, insn_t, reg_t);
class sim_t;
class trap_t;
// this class represents one processor in a RISC-V machine.
class processor_t
// architectural state of a RISC-V hart
struct state_t
{
public:
processor_t(sim_t* _sim, mmu_t* _mmu, uint32_t _id);
~processor_t();
void reset(bool value);
void step(size_t n, bool noisy); // run for n cycles
void deliver_ipi(); // register an interprocessor interrupt
bool running() { return run; }
void set_pcr(int which, reg_t val);
void set_interrupt(int which, bool on);
reg_t get_pcr(int which);
mmu_t* get_mmu() { return &mmu; }
void register_insn(uint32_t match, uint32_t mask, insn_func_t rv32, insn_func_t rv64);
void reset();
private:
sim_t& sim;
mmu_t& mmu; // main memory is always accessed via the mmu
// user-visible architected state
// user-visible state
reg_t pc;
regfile_t<reg_t, NXPR, true> XPR;
regfile_t<freg_t, NFPR, false> FPR;
@ -46,17 +29,47 @@ private:
reg_t epc;
reg_t badvaddr;
reg_t evec;
reg_t ptbr;
reg_t pcr_k0;
reg_t pcr_k1;
reg_t cause;
reg_t tohost;
reg_t fromhost;
uint32_t id;
uint32_t sr; // only modify the status register using set_pcr()
uint32_t fsr;
uint32_t count;
uint32_t compare;
reg_t load_reservation;
};
// this class represents one processor in a RISC-V machine.
class processor_t
{
public:
processor_t(sim_t* _sim, mmu_t* _mmu, uint32_t _id);
~processor_t();
void reset(bool value);
void step(size_t n, bool noisy); // run for n cycles
void deliver_ipi(); // register an interprocessor interrupt
bool running() { return run; }
reg_t set_pcr(int which, reg_t val);
uint32_t set_fsr(uint32_t val); // set the floating-point status register
void set_interrupt(int which, bool on);
reg_t get_pcr(int which);
uint32_t get_fsr() { return state.fsr; }
mmu_t* get_mmu() { return &mmu; }
state_t* get_state() { return &state; }
void yield_load_reservation() { state.load_reservation = (reg_t)-1; }
void register_insn(uint32_t match, uint32_t mask, insn_func_t rv32, insn_func_t rv64);
private:
sim_t& sim;
mmu_t& mmu; // main memory is always accessed via the mmu
state_t state;
uint32_t id;
bool run; // !reset
struct opcode_map_entry_t
@ -70,22 +83,14 @@ private:
std::multimap<uint32_t, opcode_map_entry_t> opcode_map;
void take_interrupt(); // take a trap if any interrupts are pending
void set_fsr(uint32_t val); // set the floating-point status register
void take_trap(reg_t t, bool noisy); // take an exception
void take_trap(reg_t pc, trap_t& t, bool noisy); // take an exception
void disasm(insn_t insn, reg_t pc); // disassemble and print an instruction
friend class sim_t;
friend class mmu_t;
friend class htif_isasim_t;
#define DECLARE_INSN(name, match, mask) \
reg_t rv32_ ## name(insn_t insn, reg_t pc); \
reg_t rv64_ ## name(insn_t insn, reg_t pc);
#include "opcodes.h"
#undef DECLARE_INSN
insn_func_t decode_insn(insn_t insn);
reg_t illegal_instruction(insn_t insn, reg_t pc);
};
#endif

18
riscv/sim.cc

@ -18,9 +18,9 @@ static void handle_signal(int sig)
signal(sig, &handle_signal);
}
sim_t::sim_t(size_t _nprocs, size_t mem_mb, const std::vector<std::string>& args)
: htif(new htif_isasim_t(this, args)),
procs(_nprocs), current_step(0), current_proc(0), debug(false)
sim_t::sim_t(size_t nprocs, size_t mem_mb, const std::vector<std::string>& args)
: htif(new htif_isasim_t(this, args)), procs(std::max(nprocs, size_t(1))),
current_step(0), current_proc(0), debug(false)
{
signal(SIGINT, &handle_signal);
// allocate target machine's memory, shrinking it as necessary
@ -38,11 +38,9 @@ sim_t::sim_t(size_t _nprocs, size_t mem_mb, const std::vector<std::string>& args
fprintf(stderr, "warning: only got %lu bytes of target mem (wanted %lu)\n",
(unsigned long)memsz, (unsigned long)memsz0);
mmu = new mmu_t(mem, memsz);
debug_mmu = new mmu_t(mem, memsz);
if (_nprocs == 0)
_nprocs = 1;
for (size_t i = 0; i < _nprocs; i++)
for (size_t i = 0; i < procs.size(); i++)
procs[i] = new processor_t(this, new mmu_t(mem, memsz), i);
}
@ -54,7 +52,7 @@ sim_t::~sim_t()
delete procs[i];
delete pmmu;
}
delete mmu;
delete debug_mmu;
free(mem);
}
@ -100,7 +98,7 @@ void sim_t::step(size_t n, bool noisy)
if (current_step == INTERLEAVE)
{
current_step = 0;
procs[current_proc]->mmu.yield_load_reservation();
procs[current_proc]->yield_load_reservation();
if (++current_proc == procs.size())
current_proc = 0;
}
@ -117,7 +115,7 @@ bool sim_t::running()
void sim_t::stop()
{
procs[0]->tohost = 1;
procs[0]->state.tohost = 1;
while (!htif->done())
htif->tick();
}

2
riscv/sim.h

@ -38,7 +38,7 @@ private:
std::auto_ptr<htif_isasim_t> htif;
char* mem; // main memory
size_t memsz; // memory size in bytes
mmu_t* mmu; // debug port into main memory
mmu_t* debug_mmu; // debug port into main memory
std::vector<processor_t*> procs;
void step(size_t n, bool noisy); // step through simulation

17
riscv/trap.cc

@ -1,12 +1,15 @@
// See LICENSE for license details.
#include "trap.h"
#include "processor.h"
#include <cstdio>
const char* trap_name(trap_t t)
const char* trap_t::name()
{
#define DECLARE_TRAP(x) "trap_"#x
static const char* names[] = { TRAP_LIST };
#undef DECLARE_TRAP
const char* fmt = uint8_t(which) == which ? "trap #%u" : "interrupt #%u";
sprintf(_name, fmt, uint8_t(which));
return _name;
}
return (unsigned)t >= sizeof(names)/sizeof(names[0]) ? "unknown" : names[t];
void mem_trap_t::side_effects(state_t* state)
{
state->badvaddr = badvaddr;
}

75
riscv/trap.h

@ -3,35 +3,58 @@
#ifndef _RISCV_TRAP_H
#define _RISCV_TRAP_H
#define TRAP_LIST \
DECLARE_TRAP(instruction_address_misaligned), \
DECLARE_TRAP(instruction_access_fault), \
DECLARE_TRAP(illegal_instruction), \
DECLARE_TRAP(privileged_instruction), \
DECLARE_TRAP(fp_disabled), \
DECLARE_TRAP(reserved0), \
DECLARE_TRAP(syscall), \
DECLARE_TRAP(breakpoint), \
DECLARE_TRAP(load_address_misaligned), \
DECLARE_TRAP(store_address_misaligned), \
DECLARE_TRAP(load_access_fault), \
DECLARE_TRAP(store_access_fault), \
DECLARE_TRAP(vector_disabled), \
DECLARE_TRAP(vector_bank), \
DECLARE_TRAP(vector_illegal_instruction), \
DECLARE_TRAP(reserved1), \
#define DECLARE_TRAP(x) trap_##x
enum trap_t
#include "decode.h"
class state_t;
class trap_t
{
public:
trap_t(reg_t which) : which(which) {}
virtual const char* name();
virtual void side_effects(state_t* state) {}
reg_t cause() { return which; }
private:
char _name[16];
reg_t which;
};
class mem_trap_t : public trap_t
{
TRAP_LIST
NUM_TRAPS
public:
mem_trap_t(reg_t which, reg_t badvaddr)
: trap_t(which), badvaddr(badvaddr) {}
void side_effects(state_t* state);
private:
reg_t badvaddr;
};
#define DECLARE_TRAP(n, x) class trap_##x : public trap_t { \
public: \
trap_##x() : trap_t(n) {} \
const char* name() { return "trap_"#x; } \
};
#undef DECLARE_TRAP
struct interrupt_t { interrupt_t(int which) : i(which) {} int i; };
struct halt_t {}; // thrown to stop the processor from running
#define DECLARE_MEM_TRAP(n, x) class trap_##x : public mem_trap_t { \
public: \
trap_##x(reg_t badvaddr) : mem_trap_t(n, badvaddr) {} \
const char* name() { return "trap_"#x; } \
};
extern "C" const char* trap_name(trap_t t);
DECLARE_TRAP(0, instruction_address_misaligned)
DECLARE_TRAP(1, instruction_access_fault)
DECLARE_TRAP(2, illegal_instruction)
DECLARE_TRAP(3, privileged_instruction)
DECLARE_TRAP(4, fp_disabled)
DECLARE_TRAP(5, reserved0)
DECLARE_TRAP(6, syscall)
DECLARE_TRAP(7, breakpoint)
DECLARE_MEM_TRAP(8, load_address_misaligned)
DECLARE_MEM_TRAP(9, store_address_misaligned)
DECLARE_MEM_TRAP(10, load_access_fault)
DECLARE_MEM_TRAP(11, store_access_fault)
DECLARE_TRAP(12, vector_disabled)
DECLARE_TRAP(13, vector_bank)
DECLARE_TRAP(14, vector_illegal_instruction)
#endif

Loading…
Cancel
Save