1
0
mirror of https://github.com/livingcomputermuseum/ContrAlto.git synced 2026-01-23 02:47:48 +00:00
Josh Dersch 4bc85daa36 New hardware implementation:
- Orbit controller: implemented and passes ROS-less diagnostics
- ROS: In progress, not functional
- DAC: For Ted Kaehler's Smalltalk Music system (FM and Sampling).  Works, generates audio and can capture to WAV file.
- Organ keybard: Stub, enough implemented to make the music system happy (so it will play back music and not crash.)

Some minor cleanup.

New dependency on NAudio package for DAC playback.  Installer updated to include NAudio lib.
2017-05-12 17:23:34 -07:00

640 lines
19 KiB
C#

using Contralto.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Contralto.IO
{
/// <summary>
/// Implements the Orbit controller -- hardware for generating
/// rasters to be sent to a ROS (Raster Output Scanner) device
/// such as a laser printer.
/// </summary>
public class OrbitController
{
public OrbitController(AltoSystem system)
{
_system = system;
_ros = new DoverROS(system);
_refreshEvent = new Event(_refreshInterval, null, RefreshCallback);
Reset();
}
public bool RefreshTimerExpired
{
get { return _refresh; }
}
public bool IACS
{
get { return _iacs; }
}
public bool SLOTTAKE
{
get { return _slottake; }
}
public int FA
{
get { return _fa; }
}
public void Reset()
{
//
// A Reset performs at least the following functions:
// FA <- 0, SLOTTAKE <- 0, band buffer A is assigned
// to the image buffer, the status and control dialogs
// with the adapter are reset.
//
_fa = 0;
_slottake = false;
_image = _a;
_output = _b;
_incon = false;
_outputY = 0;
_outputX = 0;
_run = false;
_iacs = false;
_refresh = false;
_goAway = false;
_behind = false;
_stableROS = true;
_badROS = false;
Log.Write(LogComponent.Orbit, "Orbit reset.");
}
public bool Wakeup
{
get
{
return _run &&
((_refresh || !_goAway) &&
(true || !_iacs)); // tautology -- the 'true' is "FIFO ready" and since I don't implement the FIFO (yet...)
}
}
public void STARTF(ushort value)
{
// Kick off the refresh timer if it's not already pending
// Wake up task, etc.
_run = true;
_system.CPU.WakeupTask(CPU.TaskType.Orbit);
// "IACS is cleared by StartIO(4)"
_iacs = false;
if (!_refreshRunning)
{
_refreshRunning = true;
_refreshEvent.TimestampNsec = _refreshInterval;
_system.Scheduler.Schedule(_refreshEvent);
}
Log.Write(LogComponent.Orbit, "Orbit started.");
}
public void Stop()
{
_run = false;
_system.CPU.BlockTask(CPU.TaskType.Orbit);
//Log.Write(LogComponent.Orbit, "Orbit blocked.");
}
public void Control(ushort value)
{
//
// This function transfers 16 bits of control information to Orbit.
//
//
// This 8-bit field has two different interpretations, depending on
// the setting of the WHICH field.
//
int auxControl = (value & 0xff00) >> 8;
//
// This bit, if 1, will reset Orbit entirely.
//
if ((value & 0x1) != 0)
{
Reset();
}
//
// This bit controls refresh logic, and should normally be 0.
//
if ((value & 0x2) != 0)
{
_refresh = false;
}
//
// This bit controls the use to which the 8-bit auxControl field is
// put. If WHICH=0, auxControl is interpreted as an address (range 0
// to 63) into the adapter status memory: when OrbitStatus is next
// interrogated, 4 status bits (with numbers 4*auxControl to
// 4 *auxControl + 3 will be reported. If WHICH=1, auxControl is used
// to set FA.
//
if ((value & 0x4) != 0)
{
_fa = auxControl;
_outputY = _fa;
// "The setting of FA provided by the Alto is examined only when
// Orbit reads out the last 16 bits of a scanline..."
// (So we *should* leave _outputY alone here).
}
else
{
_statusAddress = auxControl & 0x3f;
}
//
// This bit controls microcode wakeups, and should normally be 0.
//
if ((value & 0x8) != 0)
{
_goAway = true;
_system.CPU.BlockTask(CPU.TaskType.Orbit);
}
//
// This bit clears the BEHIND indicator.
//
if ((value & 0x10) != 0)
{
_behind = false;
}
//
// ESS - This bit must be 1 to enable changing the SLOTTAKE setting.
// SLOTTAKE - This bit setting, enabled by ESS above, controls the
// output buffer logic. Normally (SLOTTAKE=0), Orbit will not honor
// video data requests coming from the adapter. As soon as SLOTTAKE
// is set to 1, however, output data will be passed to the adapter when
// it demands it.
//
if ((value & 0xc0) == 0xc0)
{
_slottake = true;
}
Log.Write(LogComponent.Orbit,
"Set Control: aux {0}, reset {1} refresh {2} which {3} goaway {4} behind {5} slottake {6}",
auxControl, (value & 0x1) != 0, (value & 0x2) != 0, (value & 0x4) != 0, _goAway, _behind, _slottake);
}
public void SetHeight(ushort value)
{
//
// This command sends to Orbit a 12-bit field (value[4-15]) which is
// interpreted as the two's complement of the height of the source raster,
// in bits.
//
_height = 4096 - (value & 0xfff);
Log.Write(LogComponent.Orbit,
"Set Height: {0}", _height);
}
public void SetXY(ushort value)
{
//
// This commands sets x <- value[0-3] and y <- value[4-15].
// It is therefore used to set the starting scan-line within
// the band (x) and the vertical position of the bottom of the
// copy of the source raster (y).
_y = value & 0x0fff;
_x = (value & 0xf000) >> 12;
Log.Write(LogComponent.Orbit,
"Set XY: X={0}, Y={1}", _x, _y);
}
public void SetDBCWidth(ushort value)
{
//
// value[0-3] tells Orbit which bit (0-15) of the first
// word of raster data is the first bit to be examined.
//
_deltaBC = _deltaBCEnd = (value & 0xf000) >> 12;
//
// value[4-15] is interpreted as the width of the source
// raster, minus 1.
///
_width = (value & 0x0fff);
//
// Third, executing this function initializes a mess of logic
// relating to transferring a source raster to Orbit and sets
// IACS ("in a character segment"). After the function is
// executed it is wise to issue only OrbitFontData functions until
// the image-generation for this character terminates (i.e. IACS
// becomes 0). IACS is cleared by StartIO(4).
// Start a new character
//
_iacs = true;
_firstWord = true;
_bitCount = 0;
_cx = _x;
_cy = _y;
_scanlines = (_x + (_width + 1) < 16) ? (_width + 1): 16 - _x;
Log.Write(LogComponent.Orbit,
"Set DBCWidth: DeltaBC {0}, Width {1}", _deltaBC, _width);
}
public void WriteFontData(ushort value)
{
//
// This function is used to send 16-bit raster data words to Orbit,
// usually in a very tight loop. The loop is exited when IACS becomes
// 0, that is, when the Orbit hardware counts the width counter down to
// zero, or when the x counter overflows (i.e. when it would count up
// from 15 to 16).
//
if (!_iacs)
{
Log.Write(LogType.Error,
LogComponent.Orbit, "Unxpected OrbitFontData while IACS false.");
return;
}
//
// We insert the word one bit at a time; this is more costly
// computationally but it's a ton simpler than dealing with word
// and scanline boundaries and merging entire words at a time.
//
int startBit = 15;
if (_firstWord)
{
startBit = 15 - _deltaBC;
}
for(int i = startBit; i >=0 ;i--)
{
int bitValue = (value & (0x1 << i)) == 0 ? 0 : 1;
if (bitValue != 0 && _cy < 4096) // clip to end of band
{
SetImageRasterBit(_cx, _cy, bitValue);
}
_cy++;
_bitCount++;
_deltaBCEnd++;
if (_cy > _y + _height - 1)
{
_cy = _y;
_cx++;
_width--;
if (_cx > 15 || _width < 0)
{
_iacs = false;
Log.Write(LogComponent.DoverROS, "Image band completed.");
break;
}
}
}
_firstWord = false;
Log.Write(LogComponent.Orbit,
"Font Data: {0}", Conversion.ToOctal(value));
}
public void WriteInkData(ushort value)
{
//
// This function sets 16 bits of INK memory: INK[x,0] through INK[x,15],
// where x has been previously set with OrbitXY.
//
_ink[_x] = value;
Log.Write(LogComponent.Orbit,
"Ink Data[{0}]: {1}", _x, Conversion.ToOctal(value));
}
public void SendROSCommand(ushort value)
{
//
// This function sends a 16-bit command to the ROS.
// Another ROS command should not be issued until 60 microseconds
// have elapsed, to allow time for proper transmission of the command
// to the ROS.
//
_ros.RecvCommand(value);
Log.Write(LogComponent.Orbit,
"ROS command {0}", Conversion.ToOctal(value));
}
public ushort GetOutputDataAlto()
{
if (_slottake)
{
Log.Write(LogType.Error, LogComponent.Orbit,
"Unexpected OrbitOutputData to Alto with SLOTTAKE set.");
}
return GetOutputData();
}
public ushort GetOutputDataROS()
{
if (!_slottake)
{
Log.Write(LogType.Error, LogComponent.Orbit,
"Unexpected OrbitOutputData to ROS without SLOTTAKE set.");
}
return GetOutputData();
}
public ushort GetDeltaWC()
{
Log.Write(LogComponent.Orbit,
"Delta WC {0} ({1} bits)", (_bitCount >> 4), _bitCount);
return (ushort)(_bitCount >> 4);
}
public ushort GetDBCWidth()
{
Log.Write(LogComponent.Orbit,
"Delta DBCWidth {0},{1}", _deltaBCEnd % 16, _width);
return (ushort)((_deltaBCEnd << 12) | (_width & 0xfff));
}
public ushort GetOrbitStatus()
{
int result = _ros.ReadStatus(_statusAddress);
if (_behind)
{
result |= 0x10;
}
if (_stableROS)
{
result |= 0x20;
}
if (_incon)
{
result |= 0x40;
}
if (_badROS)
{
result |= 0x80;
}
Log.Write(LogComponent.Orbit,
"OrbitStatus {0}", Conversion.ToOctal(result));
return (ushort)result;
}
private ushort GetOutputData()
{
//
// Basically, the function OrbitOutputData reads 16 bits from the output buffer
// into the Alto.
//
// The programmer (or emulator author) needs to be aware of two tricky details
// concerning readout:
// 1. There is a 16-bit buffer (ROB) that sits between the addressing mechanism
// and the outside world (the taker of data: the Alto or the ROS adapter). As
// a result, immediately after the buffers "switch" the ROB contains the last
// 16-bits of data from the previous buffer (x=15, y=4080 through y=4095).
// The usual convention is to read out this one last word. This leaves the first
// word of the new buffer in ROB.
//
// 2. The output band buffer will not be refreshed ... (not a concern here)
//
// Return the last value from ROB
ushort value = _rob;
// Update ROB
_rob = _output[_outputX, _outputY];
// Clear read word
_output[_outputX, _outputY] = 0;
//
// Move to next word
//
_outputY++;
if (_outputY == 256)
{
Log.Write(LogComponent.DoverROS,
"OutputData - scanline {0} completed", _outputX);
_outputY = _fa;
_outputX++;
if (_outputX == 16)
{
// Done reading output band -- swap buffers!
Log.Write(LogComponent.DoverROS,
"OutputData - buffer read completed.");
SwapBuffers();
_outputX = 0;
}
}
Log.Write(LogComponent.Orbit,
"OutputData {0}", Conversion.ToOctal(value));
return value;
}
private void SwapBuffers()
{
ushort[,] _temp = _image;
_image = _output;
_output = _temp;
_outputY = _fa;
_outputX = 0;
//
// If input is still being written,
// the Alto is now behind the consumer...
//
if (!_goAway)
{
Console.WriteLine("BEHIND");
_behind = true;
}
else
{
// "The buffer switch will turn off GOAWAY and consequently
// allow wakeups again."
_goAway = false;
_system.CPU.WakeupTask(CPU.TaskType.Orbit);
}
// Flip buffer bit
_incon = !_incon;
}
private void SetImageRasterBit(int x, int y, int bitValue)
{
// Pick out the word we're interested in
int wordAddress = y >> 4;
ushort inputWord = _image[x, wordAddress];
// grab the ink bit and OR it in.
int inkBit = (_ink[x] & (0x8000 >> (y % 16)));
_image[x, wordAddress] = (ushort)(inputWord | inkBit);
}
private void RefreshCallback(ulong timeNsec, ulong skewNsec, object context)
{
_refresh = true;
//Log.Write(LogComponent.Orbit, "Refresh signal raised.");
if (_run)
{
_system.CPU.WakeupTask(CPU.TaskType.Orbit);
// do it again
_refreshEvent.TimestampNsec = _refreshInterval;
_system.Scheduler.Schedule(_refreshEvent);
}
else
{
//Log.Write(LogComponent.Orbit, "RUN deasserted, Refresh aborted.");
_refreshRunning = false;
}
}
// Run status
private bool _run;
// Raster information
private int _height; // height (in bits)
private int _x; // scanline
private int _y; // bit position in scanline
private int _width; // raster width of character
private int _bitCount; // count of processed bits; used to calculate DeltaWC.
private int _deltaBC; // bit of first word to start with
private int _deltaBCEnd; // number of bits left in last word rasterized
private int _scanlines;
// FA -- "First Address" of each scanline -- a value between
// 0 and 255 indicating the starting word to be read by the
// output device (either the Alto or the ROS).
private int _fa;
// SLOTTAKE - indicates that the ROS takes data (not the Alto)
private bool _slottake;
// Address to pull ROS status bits from
private int _statusAddress;
// Signals completion of input from Alto
private bool _goAway;
// Whether the Alto has fallen behind the ROS's consumption
private bool _behind;
// Current raster position
private bool _firstWord;
private int _cx;
private int _cy;
//
// IACS ("in a character segment") is set by OrbitDBCWidthSet
// and remains set until:
// - StartIO(4) is invoked
// - The Width counter counts down to zero
// - The current band is completed (the x counter overflows to 16)
//
private bool _iacs;
//
// Refresh timer expiry
//
private bool _refresh;
//
// Output band position and data
//
private int _outputX;
private int _outputY; // in words, starting at FA
private ushort _rob; // Read Output Buffer
//
// Buffer status -- if 0, A is the image buffer, B is the
// output buffer. If 1, these are swapped.
//
private bool _incon;
//
// ROS status
//
private bool _badROS;
private bool _stableROS;
// Raster bands
private ushort[,] _a = new ushort[16, 256]; // 16x4096 bits
private ushort[,] _b = new ushort[16, 256];
// Ink data
private ushort[] _ink = new ushort[16];
// buffer references that are swapped
private ushort[,] _image;
private ushort[,] _output;
//
// Refresh event and interval
private bool _refreshRunning;
private Event _refreshEvent;
private readonly ulong _refreshInterval = 2 * Conversion.MsecToNsec; // 2ms in nsec
//
// The ROS we talk to to get raster onto a "page"
//
private DoverROS _ros;
private AltoSystem _system;
}
}