|
|
nono 1.7.0
//
// nono
// Copyright (C) 2020 nono project
// Licensed under nono-license.txt
//
// 例外処理について。
// 例外のうち、リセット(優先度1)、命令アクセス(2)、未実装命令(3)、特権違反(4)
// とその他の命令内部発生例外(5、トラップやゼロ除算など) は、この命令に
// 直接起因しているため precise 例外に分類される。
// 一方、割り込み(優先度6)、FPU imprecise(7)、データアクセス(8) は imprecise
// 例外に分類される。
//
// データアクセス例外について。
// Figure 6-3 参照。例外 (例えば割り込み例外) 起動時にデータユニットが稼働中
// ならデータアクセスの完了を待つ。データアクセスが失敗(fault)すればその状態
// でシャドウレジスタをフリーズするので、割り込みの受け付けとデータアクセス
// 例外が同じ命令内で起きると、その命令直後の境界での例外はベクタは割り込み
// 例外 (1番)だが DMTx が VALID という混合状態になる。
// 実際 OpenBSD のカーネルはこの状態を認識してわざわざ処理している。
//
// 実機は、上記の内部例外を起こす整数ユニットとデータユニットが並行して
// 動けるため、割り込み以外の例外でもデータアクセス例外が同時に起きるかも
// 知れないが、ここでは考えない。
// また割り込みと FPU imprecise も同時に起きる可能性はあるが未調査。
// FPU imprecise とデータアクセスについても未調査。
#include "m88100.h"
#include "debugger.h"
#include "event.h"
#include "mpu88xx0.h"
#include "scheduler.h"
#define OP_DEF(name) void __CONCAT(MPU88xx0Device::op_,name)()
#define OP_FUNC(name) __CONCAT(op_,name)()
#include "m88100ops.cpp"
// プリフェッチステージ(?)。
// 命令サイクルは、自然に書けば、
// (1) パイプラインをシフトし、FIP の指すところをフェッチ。
// (2) デバッガはここに入れたい。
// (3) XIP(opX) を解釈、実行。
// の繰り返しになるが、デバッガは Exec() から戻ったところでスケジューラから
// 呼び出される構造のため、順序をずらして 1回の Exec() では (3)->(1) を実行
// してからスケジューラに戻って (2) を実行する、の順にする。
// そのため、リセット例外では1回プリフェッチを実行しておく必要がある。
void
MPU88xx0Device::Prefetch()
{
// shift pipeline
reg.opX = reg.opF;
reg.xip = reg.nip;
// prefetch next inst
fetch();
}
// 1命令実行のコールバック。
void
MPU88xx0Device::ExecNormal(Event *ev)
{
uint64 cycle_start = used_cycle;
uint32 op12;
buswait = 0;
// この時点で実行しようとする命令が opX に入っている
if (__predict_false(OpIsBusErr(reg.opX))) {
// プリフェッチしていた命令がバスエラー
Exception(EXCPRI_INST);
} else {
op12 = op32_to_12(reg.opX);
switch (op12) {
#include "m88100switch.inc"
default:
OP_FUNC(illegal);
break;
}
}
// 例外発生か。
if (__predict_false(excep_pending != 0)) {
if (__predict_false(resetting)) {
// 命令実行の先でリセットが起きたら以降を全部キャンセルして帰る。
// システムコントローラのリセットポートアクセスで起きる。
return;
}
// 内部(precise)例外のうち優先度の最も高いのを一つ実行。
// x & (-x) で立ってる最下位ビットだけにする。
uint32 pri = excep_pending & (-excep_pending);
excep_pending &= ~pri;
ExceptionCore(pri);
}
used_cycle++;
Prefetch();
int cycle = used_cycle - cycle_start;
ev->time = cycle * clock_tsec + buswait;
scheduler->StartEvent(ev);
}
// 例外処理。ExecNormal() の下請け。
// 例外を発生させるのはこれではなく、この下にある *Exception() のほう。
void
MPU88xx0Device::ExceptionCore(uint32 pri)
{
uint32 vec;
uint syscall = 0;
// 例外種別(優先度)からベクタ番号に変換。
uint primask = pri;
switch (pri) {
case EXCPRI_ERROR:
vec = EXCVEC_ERROR;
break;
case EXCPRI_INST:
vec = EXCVEC_INST;
break;
case EXCPRI_UNIMPL_OP:
vec = EXCVEC_UNIMPL_OP;
break;
case EXCPRI_PRIV:
vec = EXCVEC_PRIV;
break;
case EXCPRI_INTERNAL:
case EXCPRI_TRAP:
// ベクタは別に指定されている。
vec = excep_vector;
excep_vector = 0;
break;
case EXCPRI_OUTER:
// 割り込みとデータアクセス例外は同時に処理する。
// OUTER は、割り込み、データアクセス、FPU imprecise の少なくとも
// どれかが立っているという代表値なので、ここで欲しいのは
// excep_pending の残っているビット (複数かも知れない) のほう。
// FPU imprecise は未調査。
primask = excep_pending;
excep_pending = 0;
pri = primask & (-primask);
switch (pri) {
case EXCPRI_INTERRUPT:
vec = EXCVEC_INTERRUPT;
break;
case EXCPRI_SFU_IMPRECISE:
vec = EXCVEC_SFU1_IMPRECISE;
break;
case EXCPRI_DATA:
vec = EXCVEC_DATA;
break;
default:
assert(false);
break;
}
break;
default:
case EXCPRI_RESET: // 先に弾いてるので来ない。
assert(false);
break;
}
// 例外履歴に記録 (例外発生はブランチ履歴にも記録)。
// ベクタ 450 なら OpenBSD システムコール番号もついでに記録。
// システムコール番号は tb0 発行時点で r13 にセットされている。
// ただし 0 番は syscall(2) (間接システムコール) で、実際の番号は r2。
// 198番 __syscall(2) だと r2:r3 (なので実質 r3)。
// 非公式 NetBSD/luna88k はベクタ 128 を使っているようだ。
last_vector = vec;
excep_counter[vec]++;
if (vec == 450 || vec == 128) {
syscall = reg.r[13];
if (syscall == 0) {
syscall = reg.r[2];
} else if (syscall == 198) {
syscall = reg.r[3];
}
syscall &= 0xfff;
}
uint32 from = reg.xip | (IsSuper() ? 1 : 0);
uint32 info = 0xfc000000 | (syscall << 12) | vec;
exhist.AddEntry(from, 0, info);
brhist.AddEntry(from, 0, info);
if ((reg.psr & PSR_SFRZ)) {
if (pri != EXCPRI_TRAP) {
putlog(0, "Exception (primask=$%x) during SFRZ!", primask);
// エラー例外でもう一度報告。
// BranchHistory に繰り返しと判定されないよう to を変えておく。
pri = EXCPRI_ERROR;
vec = EXCVEC_ERROR;
last_vector = vec;
info = 0xfc000000 | vec;
exhist.AddEntry(from, 1, info);
brhist.AddEntry(from, 1, info);
} else {
putlog(0, "Trap during Exception");
}
} else {
if (vec == EXCVEC_INTERRUPT) {
putlog(2, "Exception (Interrupt)");
} else {
putlog(1, "Exception %x x=%x n=%x f=%x",
vec, reg.xip, reg.nip, reg.fip);
}
reg.sxip = reg.xip;
reg.snip = reg.nip;
reg.sfip = reg.fip;
if (pri != EXCPRI_UNIMPL_OP) {
reg.sxip |= SIP_V;
}
reg.snip |= SIP_V;
reg.sfip |= SIP_V;
if (OpIsBusErr(reg.opX)) {
reg.sxip |= SIP_E;
}
if (OpIsBusErr(reg.opF)) {
reg.snip |= SIP_E;
}
}
if ((primask & EXCPRI_DATA)) {
// パイプラインが未実装なので、PROM の DAE ハンドラが無限ループに
// なるのをごまかすハックは vm/prom.cpp 参照。
} else {
// データアクセス例外でなければ DMT の V ビットはクリア。(Table.6-3)
reg.dmt0 &= ~DM_VALID;
reg.dmt1 &= ~DM_VALID;
reg.dmt2 &= ~DM_VALID;
}
// XXX Figure 6-3 のフローだとトラップ例外の時は EPSR を保存しない
// ようだが本当だろうか。本文はトラップ例外の記載が少ない。
reg.epsr = reg.psr;
SetPSR(reg.psr | PSR_SUPER | PSR_SFD1 | PSR_IND | PSR_SFRZ);
reg.fip = reg.vbr + (vec << 3);
fetch();
// ここでベクタに飛ぶのだが、ベクタ内の2命令でおそらく必ずもう一度
// 分岐してそっちでも履歴が残るので、ここでブランチ履歴を残すのは
// ちょっと冗長という気もする。
if (OpIsBusErr(reg.opF)) {
if (pri == EXCPRI_ERROR) {
// double bus fault
// 実機は無限ループ状態
PANIC("Infinite ERROR (double bus fault)");
} else {
putlog(0, "Bus error on fetching $%08x (vector $%x)!",
reg.vbr + (vec << 3), vec);
Exception(EXCPRI_ERROR);
}
}
}
// EXCPRI_* の内部例外発生を予約する。
// 例外はこの場で処理するのではなく、命令境界で優先度の高い方から処理される
// ので、発生時に出来るのは予約することだけ。
void
MPU88xx0Device::Exception(uint32 pri)
{
excep_pending |= pri;
}
// 外部優先度の例外の発生を予約する。
void
MPU88xx0Device::OuterException(uint32 pri)
{
excep_pending |= pri;
excep_pending |= EXCPRI_OUTER;
}
// 内部例外の発生を予約する。
// vec は発生させる例外のベクタ番号。
void
MPU88xx0Device::InternalException(uint32 vec)
{
excep_vector = vec;
Exception(EXCPRI_INTERNAL);
}
// トラップ例外の発生を予約する。
// vec は発生させる例外のベクタ番号。
void
MPU88xx0Device::TrapException(uint32 vec)
{
excep_vector = vec;
Exception(EXCPRI_TRAP);
}
// flag から DMT の共通部分を作って返す。
uint32
MPU88xx0Device::MakeDMT(uint32 flag)
{
uint32 dmt;
dmt = DM_VALID;
if ((reg.psr & PSR_BO_LE))
dmt |= DM_BO;
if (IsSuper() && !(flag & DM_USR))
dmt |= DM_DAS;
return dmt;
}
// データアクセス例外(Read)、ld.[b,h,(w)] 用。
void
MPU88xx0Device::ReadDataException32(uint32 addr, uint32 flag)
{
// ミスアラインドはこの例外より前にチェックされていて、
// ここには (アクセスサイズでの) アラインドしかこない。
reg.dmt0 = MakeDMT(flag);
reg.dmt0 |= FLD_D << 7;
reg.dmt0 |= flag & DM_SIGNED;
reg.dmt0 |= (flag & DM_EN_MASK) >> (addr & 0x03);
reg.dma0 = addr & ~0x3;
reg.dmt1 &= ~DM_VALID;
reg.dmt2 &= ~DM_VALID;
putlog(1, "ReadDAE xip=%x addr=%x", reg.xip, addr);
OuterException(EXCPRI_DATA);
}
// データアクセス例外(Read)、ld.d 用。
// どちらのワードで例外が起きても first_addr は第1ワードのアドレス。
void
MPU88xx0Device::ReadDataException64(uint32 first_addr, uint32 flag)
{
// ミスアラインドはこの例外より前にチェックされていて、
// ここにはアラインドしかこない。
uint32 dmt_2nd;
uint32 dma_2nd;
reg.dmt1 &= ~DM_VALID;
reg.dmt2 &= ~DM_VALID;
// 先に2ワード目の情報を作っておく。
dmt_2nd = MakeDMT(flag);
dmt_2nd |= FLD_D2 << 7;
dmt_2nd |= DM_EN_MASK; // 32ビット有効
dma_2nd = first_addr + 4;
uint32 doub1 = (flag & DM_DOUB1);
if (doub1) {
// 1ワード目がバスエラーなら、DMx0 と DMx1 を使う。
reg.dmt0 = MakeDMT(flag);
reg.dmt0 |= doub1;
reg.dmt0 |= FLD_D << 7;
reg.dmt0 |= DM_EN_MASK; // 32ビット有効
reg.dma0 = first_addr;
reg.dmt1 = dmt_2nd;
reg.dma1 = dma_2nd;
} else {
// 2ワード目がバスエラーなら、DMx0 に2ワード目の情報だけ。
reg.dmt0 = dmt_2nd;
reg.dma0 = dma_2nd;
}
putlog(1, "ReadDAE64 xip=%x addr=%x", reg.xip, reg.dma0);
OuterException(EXCPRI_DATA);
}
// データアクセス例外(Write)、st.[b,h,(w)] 用。
void
MPU88xx0Device::WriteDataException32(uint32 addr, uint32 flag)
{
// ミスアラインドはこの例外より前にチェックされていて、
// ここには (アクセスサイズでの) アラインドしかこない。
reg.dmt0 = MakeDMT(flag);
reg.dmt0 |= (flag & DM_EN_MASK) >> (addr & 0x03);
reg.dmt0 |= DM_WRITE;
reg.dma0 = addr & ~0x3;
reg.dmd0 = rD;
reg.dmt1 &= ~DM_VALID;
reg.dmt2 &= ~DM_VALID;
putlog(1, "WriteDAE xip=%x addr=%x", reg.xip, addr);
OuterException(EXCPRI_DATA);
}
// データアクセス例外(Write)、st.d 用。
// どちらのワードで例外が起きても first_addr は第1ワードのアドレス。
void
MPU88xx0Device::WriteDataException64(uint32 first_addr, uint32 flag)
{
// ミスアラインドはこの例外より前にチェックされていて、
// ここにはアラインドしかこない。
uint32 dmt_2nd;
uint32 dma_2nd;
uint32 dmd_2nd;
reg.dmt1 &= ~DM_VALID;
reg.dmt2 &= ~DM_VALID;
// 先に2ワード目の情報を作っておく。
dmt_2nd = MakeDMT(flag);
dmt_2nd |= DM_EN_MASK;
dmt_2nd |= DM_WRITE;
dma_2nd = first_addr + 4;
dmd_2nd = rD2;
uint32 doub1 = (flag & DM_DOUB1);
if (doub1) {
// 1ワード目がバスエラーなら、DMx0 と DMx1 を使う。
reg.dmt0 = MakeDMT(flag);
reg.dmt0 |= DM_DOUB1; // ダブルワードの第1アクセスのときセット
reg.dmt0 |= DM_EN_MASK;
reg.dmt0 |= DM_WRITE;
reg.dma0 = first_addr;
reg.dmd0 = rD;
reg.dmt1 = dmt_2nd;
reg.dma1 = dma_2nd;
reg.dmd1 = dmd_2nd;
} else {
// 2ワード目がバスエラーなら、DMx0 に2ワード目の情報だけ。
reg.dmt0 = dmt_2nd;
reg.dma0 = dma_2nd;
reg.dmd0 = dmd_2nd;
}
putlog(1, "WriteDAE64 xip=%x addr=%x", reg.xip, reg.dma0);
OuterException(EXCPRI_DATA);
}
// データアクセス例外(xmem)
// xmem は .bu と (.w) しかない。
void
MPU88xx0Device::XmemDataException(uint32 addr, uint32 flag)
{
// ミスアラインドはこの例外より前にチェックされていて、
// ここには (アクセスサイズでの) アラインドしかこない。
uint32 dmt_rd;
uint32 dmt_wr;
uint32 dma;
uint32 dmd_wr;
reg.dmt1 &= ~DM_VALID;
reg.dmt2 &= ~DM_VALID;
dmt_rd = MakeDMT(flag);
dmt_rd |= DM_LOCK;
dmt_rd |= FLD_D << 7;
dmt_rd |= flag & DM_SIGNED;
dmt_rd |= (flag & DM_EN_MASK) >> (addr & 0x03);
dma = addr & ~0x3U;
dmt_wr = dmt_rd | DM_WRITE;
dmd_wr = rD;
if (cmmu[1]->fault_code != m88200::FAULT_CODE_BUSERR
|| cmmu[1]->acc_read) {
// アドレス変換フォルトまたは read 時点のバスエラー。
reg.dmt0 = dmt_rd;
reg.dma0 = dma;
reg.dmt1 = dmt_wr;
reg.dma1 = dma;
reg.dmd1 = dmd_wr;
} else {
// xmem の write 時点のバスエラー。
reg.dmt0 = dmt_wr;
reg.dma0 = dma;
reg.dmd0 = dmd_wr;
}
putlog(1, "xmemDAE xip=%x addr=%x", reg.xip, addr);
OuterException(EXCPRI_DATA);
}
// FPxS レジスタをセット
/*static*/ void
MPU88xx0Device::SetFPxS(uint32 *h, uint32 *l, double src, uint32 ts)
{
// FPHS[12]、FPLS[12] は以下の構造
//
// 3 2 1 0
// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
// +-+---------------------+---------------------------------------+
// |S| Exponent(11bit) | High Order 20 bits of Mantissa | FPHSn
// +-+---------------------+---------------------------------------+
//
// +---------------------------------------------------------------+
// | Low Order bits of Mantissa, or Integer | FPLSn
// +---------------------------------------------------------------+
//
// ts が 1 (double) の場合は IEEE754 double と同じ。
// ts が 0 (single) の場合は float を double にキャスト (拡大変換) した
// ものではないことに注意。
// S(符号ビット)はそのまま。float の Mantissa (23bit) のうち上位 20bit
// を FPHSn に、残り 3bit を FPLSn の上位 3bit に。ここまではいい。
// Exponent は float の指数部(8bit、バイアス=127) の値を *そのまま*
// 符号拡張(?)する。
// (float)1.0 は 1.0 * 2^0 なのでバイアス込みの指数部は 127、
// (float)2.0 は 1.0 * 2^1 なのでバイアス込みの指数部は 128。
// バイアス込みにした値は符号なし数のはずだが、ここではこれを 11bit に
// 符号拡張して FPHSn の Exponent 部に格納するようだ。取り出す際に下位
// 8bit しか取り出さなければ害はないし、符号拡張する方がハードウェア的に
// 都合がよかったとかだろうか。
// 仮に普通に double に変換すると double の指数部バイアスは 1023 なので
// バイアス込みの指数部は先程の例だとそれぞれ 1023、1024 になるが
// これではないということ。
union64 u;
u.q = d2u(src);
if (ts == 0) {
// single
uint32 exp;
// src は普通の double なのでこの指数部を取り出す
uint32 double_biased = (u.h >> 20) & 0x7ff;
if (double_biased == 0) {
// Zero
exp = 0;
} else if (double_biased == 0x7ff) {
// Inf, NAN
exp = 0x7ff00000;
} else {
// double のバイアスを外して float のバイアスを足す
int float_biased = (double_biased - 1023) + 127;
// この 8bit 値を 11bit に符号拡張して所定の位置へ
exp = ((int32)(float_biased << 24)) >> 4;
exp &= ~0x80000000;
}
*h = (u.h & 0x800fffff) | exp;
*l = u.l;
} else {
// double
*h = u.h;
*l = u.l;
}
}
// FPxS1 レジスタをセット
void
MPU88xx0Device::SetFPS1(double src, uint32 t1)
{
SetFPxS(®.fphs1, ®.fpls1, src, t1);
}
// FPxS2 レジスタをセット
void
MPU88xx0Device::SetFPS2(double src, uint32 t2)
{
SetFPxS(®.fphs2, ®.fpls2, src, t2);
}
void
MPU88xx0Device::SetFPS1(uint32 src)
{
reg.fpls1 = src;
}
void
MPU88xx0Device::SetFPS2(uint32 src)
{
reg.fpls2 = src;
}
// FPR[HL] レジスタに double 値をセット
// - それ以外のフィールドは保存する XXX ここでクリアしてもいいか?
// - XXX Guard, Round, Sticky (, AddOne) はまだない
void
MPU88xx0Device::SetFPRx(double src)
{
union64 u;
uint32 exp;
u.q = d2u(src);
// Sign は FPRH
reg.fprh &= ~(FPRH_SIGN | FPRH_MANT); // Mantissa もついでにクリア
reg.fprh |= u.h & 0x80000000;
// Exp は FPIT。
// double は 11bit だが FPIT::RESEXP は 12bit なので 1bit 符号拡張する。
// 符号付き数(正確には負数)の右シフトは実装依存だが gcc, clang は
// 算術右シフトになる。
//
// 3 2
// 1 0 9 8 7 6 5 4 3 2 1 0 9
// +-+---------------------+-
// double |S| Exponent(11bit) |
// +-+---------------------+-
//
// +---------------------+-+-
// u.h<<1 | Exponent(11bit) |X|
// +---------------------+-+-
//
// +-----------------------+-
// exp>>1 | Exponent(12bit) |
// +-----------------------+-
exp = u.h << 1;
exp = (int32)exp >> 1;
reg.fpit &= ~FPIT_RESEXP;
reg.fpit |= exp & FPIT_RESEXP;
// Mantissa は FPRH, FPRL
// 上位側(FPRH)には最上位(実数桁)の隠しビットを含む。
reg.fprh |= (u.h & 0x000fffff);
reg.fprh |= FPRH_1;
reg.fprl = u.l;
}
// FP Precise 例外。浮動小数点数1つの場合。
void
MPU88xx0Device::FPPreciseException(uint32 cause, double s2)
{
SetFPS2(s2, m88100opf_FP_T2(reg.opX));
FPPreciseException(cause);
}
// FP Precise 例外。浮動小数点数2つの場合。
void
MPU88xx0Device::FPPreciseException(uint32 cause, double s1, double s2)
{
SetFPS1(s1, m88100opf_FP_T1(reg.opX));
SetFPS2(s2, m88100opf_FP_T2(reg.opX));
FPPreciseException(cause);
}
// FP Precise 例外。
void
MPU88xx0Device::FPPreciseException(uint32 cause)
{
assert(cause == FPECR_FIOV ||
cause == FPECR_FUNIMP ||
cause == FPECR_FPRV ||
cause == FPECR_FROP ||
cause == FPECR_FDVZ);
reg.fpecr = cause;
// FPPT(FP Precise operation Type Register)
reg.fppt = reg.opX & 0xffe0; // Opcode, T1, T2, TD は opX と同じ位置
reg.fppt |= m88100opf_D(reg.opX); // DEST
InternalException(EXCVEC_SFU1_PRECISE);
}
// FP Imprecise 例外。
void
MPU88xx0Device::FPImpreciseException(uint32 cause)
{
assert(cause == FPECR_FUNF ||
cause == FPECR_FOVF ||
cause == FPECR_FINX);
reg.fpecr = cause;
// FPIT(FP Imprecise operation Type Register)
reg.fpit &= FPIT_RESEXP;
reg.fpit |= reg.opX & FPIT_OPCODE;
reg.fpit |= (reg.opX & 0x00000020) << 5; // DESTSIZ
reg.fpit |= (reg.fpcr & 0x0000001f) << 5; // EFINV,EFDVZ,EFUNF,EFOVR,EFINX
reg.fpit |= m88100opf_D(reg.opX); // DEST
// FPRH(FP Result High Register)
reg.fprh &= ~FPRH_RNDMODE;
reg.fprh |= (reg.fpcr & FPCR_RM) << 14;
OuterException(EXCPRI_SFU_IMPRECISE);
}
OP_DEF(illegal)
{
// 不当命令例外。
putlog(0, "Illegal instruction %08x", (uint32)reg.opX);
Exception(EXCPRI_UNIMPL_OP);
}
//
// m88100reg の static 変数
//
// fcr のマスク
/*static*/ const uint32 m88100reg::fcr_mask[11] = {
M88100::FPECR_MASK,
0xffffffff, // fphs1
0xffffffff, // fpls1
0xffffffff, // fphs2
0xffffffff, // fpls2
M88100::FPPT_MASK,
M88100::FPRH_MASK,
0xffffffff, // fprl
M88100::FPIT_MASK,
M88100::FPSR_MASK,
M88100::FPCR_MASK,
};
/*static*/ const char * const m88100reg::sipname[3] = {
"sxip", "snip", "sfip",
};
/*static*/ const char * const m88100reg::dmt_en_str[16] = {
"----",
"---B",
"--B-",
"--HH",
"-B--",
"-1-1", // not used normally
"-11-", // not used normally
"-111", // not used normally
"B---",
"1--1", // not used normally
"1-1-", // not used normally
"1-11", // not used normally
"HH--",
"11-1", // not used normally
"111-", // not used normally
"LLLL",
};
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.