Fix 68000 ASR carry and extend for oversized shifts

This commit is contained in:
Chris Hopper committed 2026-10-22 16:06:15 +01:00
1 parent 0f49993b4d
commit 8aaae8cbc1
5 files changed
+104 -1

No files matched your search

@@ -0,0 +1,7 @@
cmake_minimum_required(VERSION 3.19)
project(CLKM68kRightShift LANGUAGES CXX)
add_executable(clk-m68k-right-shift main.cpp)
target_compile_features(clk-m68k-right-shift PRIVATE cxx_std_20)
target_include_directories(clk-m68k-right-shift PRIVATE ../..)
enable_testing()
add_test(NAME m68k-right-shift COMMAND clk-m68k-right-shift)
+46
View File
@@ -0,0 +1,46 @@
// SPDX-License-Identifier: MIT
// Original, firmware-free stimuli and a one-bit-at-a-time specification model.
#pragma once
#include <cstdint>
#include <vector>
namespace RightShiftTest {
struct Case {
uint32_t value, count, result;
uint16_t ccr, opcode, initial_ccr;
unsigned width;
bool logical, immediate;
};
inline std::vector<Case> cases(bool logical) {
std::vector<Case> out;
for(unsigned width : {8u, 16u, 32u}) {
const uint32_t mask = uint32_t(0xffffffffu >> (32-width));
const uint32_t sign = uint32_t(1) << (width-1);
for(uint32_t low : {0u, 1u, sign-1, sign, mask, sign | 0x25u})
for(unsigned x : {0u, 1u})
for(bool immediate : {false, true}) {
const std::vector<uint32_t> counts = immediate ?
std::vector<uint32_t>{1,2,3,4,5,6,7,8} :
std::vector<uint32_t>{0,1,width-1,width,width+1,63,64,65,128,0xddb92f2c};
for(uint32_t count : counts) {
uint32_t v = low & mask, carry = 0;
const unsigned n = count & 63;
for(unsigned i=0; i<n; ++i) {
carry = v & 1;
v = (v >> 1) | (!logical ? v & sign : 0);
}
const uint16_t ccr = uint16_t(((n ? carry : x)<<4) |
((v & sign) ? 8 : 0) | (!v ? 4 : 0) | (n ? carry : 0));
// Preserve upper D3 bits for byte/word operations.
const uint32_t upper = 0x5aa55aa5u & ~mask;
const uint16_t size = width == 8 ? 0 : width == 16 ? 0x40 : 0x80;
const uint16_t opcode = uint16_t(0xe003 | size | (logical ? 8 : 0) |
(immediate ? (count & 7)<<9 : 0x820));
out.push_back({upper | low, count, upper | v, ccr, opcode,
uint16_t(0x0f | x<<4), width, logical, immediate});
}
}
}
return out;
}
}
+17
View File
@@ -0,0 +1,17 @@
# Independent 68000 right-shift regression
Original MIT-licensed test stimuli. Requires CMake 3.19+, a C++20 compiler and the CPU headers in this checkout; no firmware, disk images or external libraries are required.
From the repository root:
```sh
cmake -S Experiments/M68kRightShift -B build/m68k-right-shift
cmake --build build/m68k-right-shift --config Release
ctest --test-dir build/m68k-right-shift -C Release --output-on-failure
```
The suite checks 648 ASR and 648 LSR cases across byte, word and longword operands, six operand patterns and both initial X states. Register counts include 0, 1, width-1, width, width+1, 63, 64, 65, 128 and a large value with effective count 44. Immediate-count cases cover 1 through 8. An independent one-bit-at-a-time model supplies expected results and all X/N/Z/V/C flags, including preserved X at zero effective count. Initial N/Z/V/C are set to test recomputation.
The executable tests the shared CPU primitive, not instruction decoding. It also checks preserved supervisor/interrupt state and exactly one timing notification carrying the modulo-64 count; it does not independently measure physical instruction timings.
Specification: Motorola [M68000 Family Programmer's Reference Manual, ASL/ASR pp. 4-21 to 4-22 and LSL/LSR pp. 4-113 to 4-114](https://www.nxp.com/docs/en/reference-manual/M68000PRM.pdf#page=125). ASR shifts the sign bit back into the operand and transfers the last shifted-out bit to C/X. Zero count clears C/V and preserves X; register counts are modulo 64.
+31
View File
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: MIT
#include "Cases.hpp"
#include "InstructionSets/M68k/Perform.hpp"
#include <iostream>
using namespace InstructionSet::M68k;
struct Timing {
int count=-1, calls=0;
template<typename T> void did_shift(int n) { count=n; ++calls; }
};
template<typename T, Operation asr, Operation lsr> bool test(const RightShiftTest::Case &c) {
T result=T(c.value);
Status status; status.set_status(uint16_t(0x2700 | c.initial_ccr));
Timing timing;
if(c.logical) Primitive::shift<lsr>(c.count,result,status,timing);
else Primitive::shift<asr>(c.count,result,status,timing);
return result==T(c.result) && status.ccr()==c.ccr &&
(status.status() & 0xffe0)==0x2700 && timing.calls==1 && timing.count==int(c.count&63);
}
int main() {
unsigned total=0, failures=0;
for(bool logical : {false,true}) for(const auto &c : RightShiftTest::cases(logical)) {
++total;
const bool ok = c.width==8 ? test<uint8_t,Operation::ASRb,Operation::LSRb>(c) :
c.width==16 ? test<uint16_t,Operation::ASRw,Operation::LSRw>(c) :
test<uint32_t,Operation::ASRl,Operation::LSRl>(c);
if(!ok) ++failures;
}
std::cout << "Right-shift conformance: " << failures << '/' << total << " failures\n";
return failures ? 1 : 0;
}
@@ -378,7 +378,9 @@ void shift(const uint32_t source, IntT &destination, Status &status, FlowControl
case Type::ASR:
case Type::LSR: {
if(shift > size) {
status.carry_flag = status.extend_flag = 0;
// ASR continues shifting out the sign bit; LSR shifts out zeroes.
status.carry_flag = status.extend_flag =
type == Type::ASR && (destination & Numeric::top_bit<IntT>());
} else {
status.carry_flag = status.extend_flag = (destination >> (shift - 1)) & 1;
}