Wednesday 24 February 2021

Multicore programming for DOS

Let's see if we can utilize two cores of an x86 processor in plain old DOS. Here's the code.

; nasm -O0 -fbin u.asm -o u.com

org     100h

; copy CPU1 code
mov bx, cs
mov ds, bx
mov si, cpu1
mov bx, 0x9000
mov es, bx
xor di, di
mov cx, 512
rep movsb

jmp protected
cpu1:
mov di, 0xB800
mov es, di
mov di, 2
mov al, '1'
stosb
mov al, 15
stosb
hlt
protected:
cli

mov     eax, cs
shl     eax, 4
add     eax, gdt
mov     [gdtinfo+2], eax

mov     eax, cs
shl     eax, 4
add     [start_addr], eax

lgdt    [gdtinfo]

mov     eax, cr0
or      eax, 1
mov     cr0, eax

db      0x66
db      0xEA
start_addr:
dd      start
dw      8
start:
bits    32
mov     ax, 0x10
mov     ds, ax
mov     es, ax

mov     edi, 0xB8000
mov     [edi+0], byte '0'
mov     [edi+1], byte 15

; start CPU1
mov edi, 0xFEE00300
mov [edi], dword 0x000C4500
mov ecx, 10000000
delay:
times 4 nop
loop delay
mov [edi], dword (0x000C4600 + 0x90000/0x1000)

hlt
gdt:
dd      0, 0
db      0xFF, 0xFF, 0, 0, 0, 10011010b, 11001111b, 0
db      0xFF, 0xFF, 0, 0, 0, 10010010b, 11001111b, 0
gdtinfo:
dw      $ - gdt - 1
dd      0




Can also be checked in virtualbox. I tested with my dual core laptop. If you have only one CPU, then only the number 0 will appear, if you have two CPUs, then also number 1 will appear.


This code has to be run without EMM386 and similar, because they limit access to the protected mode.

Thursday 11 February 2021

PCI - FPGA

After the previous project, I started thinking that perhaps it would be nice and possible to connect my cheap FPGA board to PCI-bus in such a manner that everything works in DOS without drivers. Non-PnP style. Well, at least IO-ports 0x388-0x38b, like used by Adlib and OPL3 cards.

I figured this would in fact require nothing besides directly attaching a bunch of pins from the PCI bus to my Cyclone II. One issue potentially being that Cyclone II isn't really 5V tolerant, but I just decided to ignore the issue and everything seemed to work fine (so far). Though, if you value your hardware, don't do it. It could break everything.


I compiled programs such as the one below for testing.

    ; nasm -fbin 3.asm -o 3.com

    org
    100h

    mov    al, 3
    mov    dx, 0x388
    out    dx, al

    int    0x20

This simply writes the value of register al to port 388h. Program executed in DOS without any drivers and resulted in immediate FPGA response, in this case changing LED states on the FPGA board. All good. So this kind of basic PCI is in fact really simple. It should be good enough to make a PCI based OPL3 sound card that works out of the box in DOS like the ISA cards back in the day.

Most music, like FM-synthesis and MIDI was played solely through IO-ports so stuff like Adlib, Gravis UltraSound and Roland MT-32 should be doable in principle. Digital Sound effects that utilized DMA and/or IRQ might be more problematic to do in a compatible manner.

There exists free SystemVerilog implementation of OPL3 which I also tested (on DE0CV) including a small Delta-sigma modulator for analog output. My plan next was to create a PCI-based OPL3 card that utilizes no other active components besides the FPGA and maybe a few regulators.

This way one could do retro gaming with a bit newer PC which doesn't come with ISA-bus. Although, it would be nice to get Sound Blaster compatible PCM working as well from DOS.

Ideally one would implement a PCIe card with similar functionalities. Unfortunately PCIe capable FPGAs tend to be somewhat expensive. Optical toslink or HDMI output for sound would also be nice. Not sure if 49.??? kHz that OPLs use work with optical or HDMI. Resampling might be nice anyway if PCM output is to be combined. Then it would also be nice to implement VGA-adapter so one could get video through HDMI with optimized timing and scaling.


Here's the Verilog. Only the wires marked by * were connected for this test.

/*
            PCI

             _
 B01        | |        A01
 B02        | |        A02
 B03        | |        A03
 B04        | |        A04
 B05        | |        A05
 B06        | | INTA   A06
 B07 INTB   | | INTC   A07
 B08 INTD   | |        A08
 B09        | |        A09
 B10        | |        A10
 B11        | |        A11
 B12        | |        A12
 B13        | |        A13
 B14        | |        A14
 B15        | |        A15
*B16 CLK    | |        A16
 B17        | |        A17
 B18        | |        A18
 B19        | |        A19
 B20 AD31   | | AD30   A20
 B21 AD29   | |        A21
 B22        | | AD28   A22
 B23 AD27   | | AD26   A23
 B24 AD25   | |        A24
 B25        | | AD24   A25
*B26 C/BE3  | |        A26
 B27 AD23   | |        A27
 B28        | | AD22   A28
 B29 AD21   | | AD20   A29
 B30 AD19   | | GND    A30
 B31        | | AD18   A31
 B32 AD17   | | AD16   A32
*B33 C/BE2  | |        A33
 B34        | | FRAME  A34*
*B35 IRDY   | |        A35
 B36        | | TRDY   A36*
*B37 DEVSEL | |        A37
 B38        | |        A38
 B39        | |        A39
 B40        | |        A40
 B41        | |        A41
 B42        | | GND    A42*
 B43        | |        A43
*B44 C/BE1  | | AD15   A44
 B45 AD14   | |        A45
 B46        | | AD13   A46
 B47 AD12   | | AD11   A47
 B48 AD10   | |        A48
 B49        |_| AD9    A49*
 B50           
 B51         _  
*B52 AD8    | | C/BE0  A52*
*B53 AD7    | |        A53
 B54        | | AD6    A54*
*B55 AD5    | | AD4    A55*
*B56 AD3    | |        A56
 B57        | | AD2    A57*
*B58 AD1    | | AD0    A58*
 B59        | |        A59
 B60        | |        A60
 B61        | |        A61
 B62        |_|        A62
*/

module C2PCI(CLK50, PCICLK, LED, FRAMEn, AD, CBE, IRDYn, TRDYn, DEVSELn);

input            CLK50;
input            PCICLK;
output reg [2:0] LED;
input            FRAMEn;
input      [9:0] AD;
input      [3:0] CBE;
input            IRDYn;
inout            TRDYn;
inout            DEVSELn;

parameter IO_address = 10'h388;
parameter CBECD_IOWrite = 4'b0011;

reg Transaction;
wire TransactionStart = ~Transaction & ~FRAMEn;
wire TransactionEnd = Transaction & FRAMEn & IRDYn;
wire Targeted = TransactionStart & (AD==IO_address) &(CBE==CBECD_IOWrite);
wire LastDataTransfer = FRAMEn & ~IRDYn & ~TRDYn;
wire DataTransfer = DevSel & ~IRDYn & ~TRDYn;
reg DevSelOE;
reg DevSel;

always @(posedge PCICLK)
case(Transaction)
  1'b0: Transaction <= TransactionStart;
  1'b1: Transaction <= ~TransactionEnd;
endcase

always @(posedge PCICLK)
case(Transaction)
  1'b0: DevSelOE <= Targeted;
  1'b1: if(TransactionEnd) DevSelOE <= 1'b0;
endcase

always @(posedge PCICLK)
case(Transaction)
  1'b0: DevSel <= Targeted;
  1'b1: DevSel <= DevSel & ~LastDataTransfer;
endcase

assign DEVSELn = DevSelOE ? ~DevSel : 1'bZ;
assign TRDYn = DevSelOE ? ~DevSel : 1'bZ;

always @(posedge PCICLK)
if(DataTransfer)
LED[2:0] <= ~AD[2:0];

endmodule

Wednesday 5 August 2020

YM3812

I had one old broken Sound Blaster ISA card and decided to pull the music chip from it to see if it worked. This was the Yamaha YM3812 or otherwise known as OPL2. It's the same chip that was used in Adlib cards. Pretty much everyone who played DOS games back in the day knew the sound of this chip.


This chip has a rather peculiar sample format, it's 13 bit floating point with 10 bits for mantissa and 3 bits for exponent. The chip requires 3.58 MHz input, uses 8 data bits and one address bit. I used a cheap FPGA board to generate the clock for the chip and fed the digital samples back to the FPGA. I converted the floating point number into regular 16-bit integer and used a software delta-sigma modulator running at 50 MHz to convert back to analog signal.


The chip contrary to the datasheets 16 bits seemed to be output 18 bits during a single SYNC cycle (SYNC is cyan in the above image, CLK is yellow). First 5 extra bits were irrelevant. According to the datasheet only first 3 bits were supposed to be irrelevant. It also appears this chips seemed to work fine with 3.3 V where in the datasheet the minimum voltage was listed as 4.5 V. Operating it with 3.3 V was convenient as my FPGA supplied this voltage and could also only accept 3.3 V logic.


The above is the signal directly measured from the 1-bit digital FPGA pin which is sigma-delta modulated (1st order) digital-to-analog conversion of the signal generated by the YM3812 chip. The sampling frequency is just so high (50 MHz) that it looks very good analog signal when low pass filtered.

Taking a running average (a kind of low pass filter) of the delta-sigma modulated signal yields an analog signal. The higher the "oversampling" rate, the more faithful the filtered signal is to the original (up to some high frequency cutoff).

Second order delta-sigma modulation shapes the noise in order to improve the signal-to-noise ratio even further at the expense of the S/N at the higher frequencies.

Looking at the spectrum, one can easily see that the quantization noise slope is much steeper for higher order delta-sigma modulations and thus can yield better signal fidelity at lower frequencies. The low frequency peak's "skirt" is due to finite sampling. The signal is actually just a delta-peak in frequency space and has no skirt (other than some vanishingly small one related to the phase noise of the digital clock, but that's another story and not relevant for this context). 
DSM is of course not limited to just 1-bit, but one can use it to improve the SNR of n-bit converters as well if they are capable of operating fast enough relatively to the signal of interest. The image is from 4-bit converter modulated by 2nd order DSM.


The effect of higher order DSMs on quantization noise.

Below, 1-bit 1st order DSM and 4-bit 2nd order DSM demonstration code in Matlab.

out = 0;
int = 0;
for x = 1:length(t)
    int = int + sin(pi*t(x)) - out(x);
    if int > 0
        out(x+1) = 1;
    else
        out(x+1) = -1;
    end
end

%%

out2 = 0;
int1 = 0;
int2 = 0;
for x = 1:length(t)
    int1 = int1 + 6*sin(pi*t(x)) - out2(x);
    int2 = int2 + int1 - out2(x);
    out2(x+1) = round(int2);
end

Saturday 1 August 2020

EGA to PVM

I bought an EGA display card for ISA recently. EGA games run without scan doubling so they looked a bit different originally on real EGA monitor when compared to VGA monitor and I hadn't actually ever seen this so I figured I'd try. Unfortunately I don't have an EGA monitor or CGA monitor (low resolution EGA is compatible with CGA-monitors) and they are quite difficult to come by. EGA as well as CGA uses digital TTL signals instead of analog RGB like VGA monitors. VGA monitors are also incompatible with CGA and EGA timings so I would have to figure something out.



I do have a PVM capable of displaying NTSC signals (basically a TV), including RGB input and since most EGA games are running in the low resolution mode with NTSC compatible timing, I figured it might be relatively easy to botch something up. PVM only accepts CSYNC so I would have to convert VSYNC and HSYNC. Unfortunately they need to be inverted so I had to use an active logic circuit for that instead of just something passive. Luckily the logic is quite simple. Another thing is that TTL levels are too high for PVM, but simply placing 1 kohm resistor in series divides the voltage down sufficiently (into 75 ohm input). Maybe this division is even a bit too much, but it's what I had at hand. Below is the schematic I used.



Unfortunately the CGA/EGA color brown is handled in a "nonlinear" fashing and would require a bit more logic to display it correctly. Here it instead of being brown looks more yellow. However, the other 15 colors are fine.


This is how it would look with the correct shade of brown.


My botched board is maybe a bit nasty, but it works fine.


Monday 20 July 2020

FPGA to ISA-bus

I bought a while ago a few of the cheap FPGA boards from ebay for 10 € a pcs and decided to test whether one could interface the old ISA bus with it. One issue is that this FPGA board is not 5 V tolerant so I decided to try with 10 kohm resistors in series with the pins and this seemed to be all that was needed or at least so far everything seems ok for standard port writes even if I'm not entirely sure if this is safe in the long run.


I ordered some rather blank ISA boards from JLCPCB for 2 € for 5 boards and soldered some tests leads to 8 data pins and 10 address pins.



The 8 data pins are located at A9-A2 and the 20 address pins are located at A31-A12. Additionally we need nIOW for I/O write which is located at B13 and GND that is located at B1.


This is how the thing looked like. I suppose one could have powered the FPGA board directly from the ISA slot, assuming it can supply enough current. That way one could make a rather standalone board, but so far I didn't try this. Actually, in this picture I have only 9 address pins connected, but it would be more standard to use 10 address pins.


I wrote a quick VERILOG to control the FPGA board LEDs. Basically all this does is set the LEDs to match the data bits when I/O write occurs and the address is 511.

module C2(LED, ADDR, DATA, nIOW);

output  [2:0] LED;
input   [8:0] ADRR;
input   [7:0] DATA;
input         nIOW;

reg     [2:0] leds;
assign LED = leds;

always @(negedge nIOW)
begin
if(ADDR == 511)
leds <= DATA;
end

endmodule

I also wrote a quick PASCAL program to test with a binary counter.

var
  a, b : byte;
  c : word;
begin
  for b := 0 to 4 do
    for a := 0 to 7 do
      for c := 0 to 32768 do ;
      for c := 0 to 32768 do port[511] := a;
    end;
  end;
end.

All seemed to work well on the first try so it appears making a rudimentary ISA device is very easy.

[http://www.hardwarebook.info/ISA]

Sunday 14 June 2020

Phosphorescence decay of LIT

I bought this phosphorescent material already a while ago, because I was fascinated by the long decay time compared to most other materials. One could faintly see the glow even after a week from just 1 second of illumination by a 1 W 445 nm laser. Even moderately higher temperature like touch by a human hand also seemed to expedite the release of the stored energy in this material significantly and this effect was clearly visible even hours after the first excitation. After long period in the dark and drained from stored energy, application of heat did not cause emissions as expected.

The seller claims it can be excited by heat, but this is of course nonsense. In general only wavelengths shorter than the emission spectrum can excite so infrared and heat can only expedite the release of the already stored energy, but not cause excitations. Excitation by heat under equilibrium condition is forbidden by the laws of thermodynamics.

It it is still true that under nonequilibrium conditions such as high intensity infrared irradiation can a process called frequency upconversion occur, but these are very rare events, do not apply to this case and do not come into conflict with the laws of thermodynamics.


I made a quick detector from a photomultiplier tube and a metal can and the proceeded to log the tube output using a raspberry pi and a digital multimeter.


It seems this material doesn't exhibit typical exponential decay one would normally expect, but rather it seems to decay first at a faster rate and then slow down. The measurements below indicate that the intensity will drop to approximately 1:10 of the initial intensity during the first hour, but will take something like 18 hours to drop to 1:100. Human eye adjusted to dark can still detect the output probably even if it's only 1:1000 of the initial. I don't know what exactly is the cause of this non-exponential behavior in this material, but I suspect there is some kind of statistical mix of different decay times at play here. Unfortunately I couldn't find my strontium aluminate right now for comparision. I guess some events are fast, some are slow and these then together will lead to this decay curve. I know I'm far from the dark counts of the tube and it's not saturating either so all should be good. The quantitative results also seem to match the qualitative results as far as my eye can tell so this really is the way this material behaves. Thiugh, after long enough it seems to settle to exponential decay so perhaps the assumption is correct. Perhaps I should also check how the emission spectrum looks like, but for that I'll have to wait for my spectrometer to arrive.


This is the same detector which can count individual photons. Although, the quantum efficiency is only slightly above 20%.


...got my spectrometer now and measured a bunch of things...


Monitor red doesn't appear to be very natural. Because I excited quinine with 404 nm laser, the peak is seen in the fluorescence spectrum as well as fluorescence (fluorescence lifetime is so short that afterglow can't be measured directly this way). LIT phosphorescence spectrum doesn't show the 445 nm excitation as the excitation is off when collecting the spectrum as phosphorescence afterglow decays slow enough. My 445 nm laser seems to be frequency upconverted from IR and the IR peak is visible as well (barely at the edge of the range for my spectrometer).

Spectrum lines of my monitor displaying all white image

Sunday 12 January 2020

Sync issues solved...

No more dropped frames, the solution was...
1) LPT2MIDI allows lower delay (only times 4 in al, dx) as opposed to SBMIDI.
2) The timer is reset every 69th frame.
3) OPL3 delay (1+3 times in al, dx) is used.


I released the sources...
1) https://github.com/eigenco/LPT2MIDI
2) https://github.com/eigenco/supaplex