Creating a complete UK101 on a low-cost FPGA board

...step by step design and implementation

by Grant Searle

For news and updates, follow me on Twitter:
Follow @zx80nut

Last update: 5th February 2014 6:45pm GMT

Please note that you are NOT allowed to reproduce any of this page elsewhere on the Web without my permission.
 


Major update history (minor updates not included here):
4th January 2014 - Initial release of this page.
12th January 2014 - Updates to PS2 keyboard to work with older monitor as well as CEGMON. TV display shifted slightly. Old monitor now included as well as CEGMON - change code and compile to use it - see below.
13th January 2014 - Full memory expansion using one SRAM chip - see below.
14th January 2014 - Wiring picture for the SRAM - see below.
18th January 2014 - Extended display (64x32) modification see below and fast speed (press F1 on keyboard) - see below

20th January 2014 - Miniboard schematics and notes added. Load and Save notes added
26th January 2014 - Updated FPGA and pics - moved PS2 keyboard to bidirectional pins 86 and 87 in readiness for kb led setting (future)


Index

Introduction
Tools needed
Important notes regarding the board being used (plus board schematics)
Step1: Determine required functional blocks
    Block diagram
    The required memory map
Step2: Connecting the blocks
Step3: Assign actual pins to the VHDL signals
Step4: Compile the VHDL and upload
Software and VHDL project download link
Using the board (wiring/connections)
Description of the modules used
    Text display
    Keyboard interface
    Serial interface
ROM definition files
Modifications
    Modification 1: Using the older monitor instead of CEGMON
    Modification 2: Boosting the RAM using one external SRAM chip
    Modification 3: Extended display - full 64x32 display (PAL)
    Modification 4: Fast CPU selection
Loading and saving programs
My other pages


Introduction

Creating a system on a chip is not as complicated as first appears if you approach it in a structured and methodical way. Many FPGA implementations of components are already freely available, and you then can use these already written, or write your own. This page will guide you through the complete process needed to take an existing computer hardware and convert it into an FPGA. If you follow the steps below then you will implement a complete UK101 with 4K RAM, keyboard, PAL TV (AV composite video) display and serial interface.

Using an additional SRAM the RAM can be expanded up to 40K plus the display resolution can be increased (changes to the monitor required to recognise the larger screen). I have already made these changes and will publish at a suitable date.

The final cost of this is surprisingly low (no need to buy the very expensive project boards) - a very low cost board is easily available that has most of what is required - very few additional (low cost) parts are needed.

This page is not intended as an introduction to VHDL - there are many pages already explaining that, but this will take you through the complete design and implementation. You can, or course, just take the VHDL project that I created and use it immediately - but it's important to understand what I did so that you can do the same yourself.

Important note: FPGA I/O pins normally have 3.3V levels, NOT 5V, so be careful when interfacing to TTL and CMOS parts.

The board and keyboard takes less than 100mA so could be powered from batteries if needed.

The board that is used in this article measures only 72mm x 51mm (2 7/8" x 2") so a very small case can be used if needed.

The board is available from many sources, eBay suppliers probably the easiest and cheapest (search for EP2C5T144C8N mini board) .


Tools needed

FPGA development software - Altera Quartus II 13 sp1 (Do not use newer versions - Cyclone II FPGAs are not supported with newer versions)
FPGA USB programmer (USB-blaster or similar)
Low-cost Altera FPGA board (EP2C5T144C8N Cyclone II FPGA on a mini board although any larger board/FPGA can be used)
Suitable pin connector wires
2x 10K resistors
PS/2 connector
1K resistor
470R resistor
RCA ("phono") connector for the video out
5V Power supply with suitable connector for the board
Breadboard/PCB
Wires with ends that can plug onto the pins on the board
PC keyboard with a PS/2 (round) connector
Optional serial interface: 3.3V USB to serial board and 3x 2K7 resistors

 


Important notes regarding the board being used

A schematic is available but was very spread out. I have compressed it to an image that can still be read when printed on A4.

This schematic of the board is available HERE (right-click and "Save as").

As you will see on the schematic, some FPGA pins on the board have connections made, so need to be accounted for when doing this or other designs on it...

To summarise the pins that are available on the connectors that need consideration...

Pin 3 - LED 1 (D2 on board) - low to light
Pin 7 - LED 2 (D4 on board) - low to light
Pin 9 - LED 3 (D5 on board) - low to light
Pin 144 - pushbutton to ground, no external pullup. Set internal pullup on FPGA configuration if used. I will use for UK101 "reset"
Pin 17 - 50MHz clock input
Pin 73 - 10uF capacitor to ground, 10K resistor to Vcc, for power up reset if needed?
Pin 26 - Connected to Vcc 1.2V - only needed for EPC28 though, so the "zero ohm" resistor could be removed and the pin used as normal.
Pin 27 - Connected to GND - only needed for EPC28 though, so the "zero ohm" resistor could be removed and the pin used as normal.
Pin 80 - Connected to GND - only needed for EPC28 though, so the "zero ohm" resistor could be removed and the pin used as normal.
Pin 81 - Connected to Vcc 1.2V - only needed for EPC28 though, so the "zero ohm" resistor could be removed and the pin used as normal.
 


The design and implementation follows.


STEP 1: Determine required functional blocks

For a computer such as the UK101, taking a look at the manual and schematic, the following were shown to be needed...

1. 6502 CPU
2. 2KB Monitor ROM (CEGMON)
3. 8KB BASIC ROM
4. 4KB RAM accessible by the CPU only
5. 1KB RAM shared between the CPU and display
6. Display counter chain / logic with +video output
7. 2KB Character ROM for the display
8. Keyboard
9. Serial/cassette interface (ACIA) - normally 300baud

For each of these, there needs to be an implementation using an implementation language such as VHDL or Verilog. Here is a breakdown of what I decided to do for each of the required blocks. I use VHDL.

1. 6502 CPU
There is already a 6502 implementaion freely available. The version on "opencores" contains bugs which prevent the BASIC interpreter working properly. Searching the net found bug fixes for this core. Once these were used the interpreter works perfectly. The version that I have included on this page is the bugfix version.

2. Monitor ROM (2KB)
Can be implemented within a memory block on the FPGA or as synthesized combinational logic. I did it both ways - the Altera Quartus II suite contains a memory core that works well. However, for a minimal 4K UK101, I wanted to avoid any external circuitry whenever possible. If I used the memory core then there would not be sufficient space for RAM, so the 4K version has a synthesized memory array containing this, freeing up the memory core for RAM use.

3. BASIC ROM (8KB)
Implemented using a single-port ROM memory block core created with Quartus II.

4. RAM accessible by the CPU only (4KB)
Implemented using a single-port RAM memory block core created with Quartus II.

5. RAM shared between the CPU and display (1KB)
Implemented using a dual-port RAM memory block core created with Quartus II. The dual-port functionality allows the CPU and display to share the RAM ensuring a flicker-free display. The isolation between the display RAM address and the cpu RAM address allows the display to run independently of the CPU so the CPU can run at any speed (1MHz "normal", up to 25MHz!)

6. Display counter chain / logic
Implemented myself with VHDL. Needs to access the shared RAM, map the RAM data to the character ROM address and serialise the ROM data to form a bitstream for the display output. This also produces the appropriate HSYNC and VSYNC signals.

7. Character ROM for the display (2KB)
Same implementation as for the monitor ROM, see above.

8. Keyboard
Two options have been implemented - a real scanned matrix identical to the original UK101 using pushbuttons, and a second implementation using a PC keyboard (PS/2 connection) with the VHDL interpreting the key up/down and setting appropriate memory bits to 100% replicate what would be seen with a scanned matrix - this ensures the code in the UK101 runs with no change whatsoever.

9. Serial/cassette interface (ACIA)
Originally tried code from the net but not happy with it, so I re-wrote it from scratch. What I have implemented is a very simple design which, however, implements nearly all of the 6850 ACIA. This design works well with this machine as well as others that I have done in VHDL. Since I was going to use serial only (not cassette) I increased the baud rate to a more useable 9600 baud with HARDWARE handshaking to prevent loss of data.

Block diagram

Here is a block-diagram for the implementation that I intend to contain within a single FPGA:

Note the isolators that are shown between each peripheral and the CPU read data bus. These are needed to prevent bus contention (several devices presenting data on the same bus) in exactly the same way as on a normal hardware implementation. These are the output enables (controlled by a "chip select" pin) on each block. Details of the chip selects and isolation are shown next.

The required memory map

The UK101 has specific areas where the hardware is to be located in the memory map. The 4K RAM implementation for this is shown below. The required address bits (to decode if that chip is active) are important and will be used to create the address decoding logic within the FPGA - see below.

Name Address range Required address bits
Program RAM (4K) 0000-0FFF 0000xxxx xxxxxxxx
BASIC ROM (8K) A000-BFFF 101xxxxx xxxxxxxx
Display RAM (1K) D000-D3FF 110100xx xxxxxxxx
Keyboard DC00-DFFF 110111xx xxxxxxxx
ACIA (serial/cassette) F000-F001 11110000 0000000x
Monitor ROM (2K) F800-FFFF 11111xxx xxxxxxxx

The required address bits are those bits that never change for the complete address range. These are used as "chip selects" in exactly the same way as would be used on discrete hardware. The remaining bits vary for that address range and are used to address a particular address within that chip. These are not relevant to the address decoding so are marked as an "x" (x=don't care).


STEP 2: Connecting the blocks

Now that the blocks have been determined, they can now be wired together. The top level VHDL defines the signals that are used and each component is attached to the appropriate signal. Below is the complete code needed for each component to attach it into the circuit.

Chip selects

Using the memory map, create the appropriate "chip select" signals, as would be done on a real machine. It is normal for chip selects to be "active low" on a real component so I have maintained this in the VHDL. All signals that I have defined that are active low have a "n_" prefix. The 6502 has a 16-bit address space and only the top address bits for each need to be checked (any "x" values are ignored) so this is easily done in VHDL with one line of code needed for each, as follows:

n_dispRamCS <= '0' when cpuAddress(15 downto 10) = "110100" else '1';
n_basRomCS <= '0' when cpuAddress(15 downto 13) = "101" else '1'; --8k
n_monitorRomCS <= '0' when cpuAddress(15 downto 11) = "11111" else '1'; --2K
n_ramCS <= '0' when cpuAddress(15 downto 12)="0000" else '1';
n_aciaCS <= '0' when cpuAddress(15 downto 1) = "111100000000000" else '1';
n_kbCS <= '0' when cpuAddress(15 downto 10) = "110111" else '1';

Bus isolation

For the CPU incoming (read) data bus is shared by all peripherals, so VHDL glue logic is needed to ensure only one peripheral presents the data onto the bus at any time.

Using the chip selects determined above, this is achieved with a small amount of VHDL coding:

cpuDataIn <=
    basRomData when n_basRomCS = '0' else
    monitorRomData when n_monitorRomCS = '0' else
    aciaData when n_aciaCS = '0' else
    ramDataOut when n_ramCS = '0' else
    dispRamDataOutA when n_dispRamCS = '0' else
    kbReadData when n_kbCS='0'
    else x"FF";

With the above code, when the CPU addresses any area not covered by the chip selects, it will read "FF" (the "else" part).

Adding the blocks

Each component is defined and attached in the following way.

1. Include the required component VHDL file within the project
2. Create a VHDL entry within the top-level code, defining the "instance" name of the component, the actual VHDL to use (as imported) and then map the signals to the I/O signals defined in the component.

instanceName : entity work.VHDLName
port map(
ComponentSignal1 => Signal1DefinedInTopLevelVHDL,
ComponentSignal2 => Signal2DefinedInTopLevelVHDL,
:
ComponentSignalN => SignalNDefinedInTopLevelVHDL);

This is repeated for each component. As each component is added the required signals are added to the top level VHDL file if not already present. Actual implementation used is shown below.

Attaching the 6502

The "T65" implementation of the 6502 processor is defined in four VHDL files. To use them, Include the following files in the project workspace:
T65.vhd
T65_Pack.vhd
T65_MCode.vhd
T65_ALU.vhd

The following VHDL code will then attach the signals defined for the T65 (bugfixed) core to the signals that I have defined for the machine:

u1 : entity work.T65
port map(
    Enable => '1',
    Mode => "00",
    Res_n => n_reset,
    Clk => cpuClock,
    Rdy => '1',
    Abort_n => '1',
    IRQ_n => '1',
    NMI_n => '1',
    SO_n => '1',
    R_W_n => n_WR,
    A(15 downto 0) => cpuAddress,
    DI => cpuDataIn,
    DO => cpuDataOut
);

Attaching the BASIC ROM

The BASIC ROM uses the bottom 13 address lines (to access the 8K address space). Attach these directly to the appropriate CPU address bits. The data ("Q") outputs of the ROM are assigned to an 8-bit signal.

u2 : entity work.BasicRom -- 8KB
port map(
    address => cpuAddress(12 downto 0),
    clock => clk,
    q => basRomData
);

Attaching the Monitor ROM

The BASIC ROM uses the bottom 11 address lines (to access the 2K address space). Attach these directly to the appropriate CPU address bits. The data ("Q") outputs of the ROM are assigned to an 8-bit signal.

u4: entity work.CegmonRom
port map
(
    address => cpuAddress(10 downto 0),
    q => monitorRomData
);

Attaching the Program RAM

The program RAM uses the bottom 12 address lines (to access the 4K address space). Attach these directly to the appropriate CPU address bits. The data ("Q") outputs of the RAM are assigned to an 8-bit signal. The data inputs can be connected directly to the CPU data output bus.
The RAM write signal is active high (1=write) so is attached to a simple combinational logic that will enable this write if the RAM is selected and the CPU write signal is active

u3: entity work.ProgRam
port map
(
    address => cpuAddress(11 downto 0),
    clock => clk,
    data => cpuDataOut,
    wren => not(n_memWR or n_ramCS),
    q => ramDataOut
);

Attaching the Display (shared - dual-port)  RAM

This is dual-port, so there are two sets of data and address busses (port a and port b). Port A is connected to the CPU and port B is connected to the display.

The program RAM uses the bottom 10 address lines (to access the 1K address space). Attach "port A" directly to the appropriate CPU address bits. The data ("q_a") outputs of the RAM are assigned to an 8-bit signal. The data inputs ("data_a") can be connected directly to the CPU data output bus.
The port A RAM write signal is active high (1=write) so is attached to a simple combinational logic that will enable this write if the RAM is selected and the CPU write signal is active.

The display never writes to port B, so the data_b and wren_b signals are tied to ground.

u8: entity work.DisplayRam
port map
(
    address_a => cpuAddress(9 downto 0),
    address_b => dispAddrB,
    clock => clk,
    data_a => cpuDataOut,
    data_b => (others => '0'),
    wren_a => not(n_memWR or n_dispRamCS),
    wren_b => '0',
    q_a => dispRamDataOutA,
    q_b => dispRamDataOutB
);

Attaching the text display

The display code has been defined as a separate component (entity) with its own VHDL file. This needs to be included in the top-level code and the relevant signals exposed by the entity definition are assigned to the signals defined in the top level VHDL.

u6 : entity work.UK101TextDisplay
port map (
    charAddr => charAddr,
    charData => charData,
    dispAddr => dispAddrB,
    dispData => dispRamDataOutB,
    clk => clk,
    sync => videoSync,
    video => video
);

Attaching the character ROM

This is attached to the text display VHDL using the following:

u7: entity work.CharRom
port map
(
    address => charAddr,
    q => charData
);

Attaching the ACIA

The serial interface (ACIA) code has been defined as a separate component (entity) with its own VHDL file. This needs to be included in the top-level code and the relevant signals exposed by the entity definition are assigned to the signals defined in the top level VHDL.

u5: entity work.bufferedUART
port map(
    n_wr => n_aciaCS or cpuClock or n_WR,
    n_rd => n_aciaCS or cpuClock or (not n_WR),
    regSel => cpuAddress(0),
    dataIn => cpuDataOut,
    dataOut => aciaData,
    rxClock => serialClock,
    txClock => serialClock,
    rxd => rxd,
    txd => txd,
    n_cts => '0',
    n_dcd => '0',
    n_rts => rts
);

Attaching the keyboard

The keyboard code has been defined as a separate component (entity) with its own VHDL file. This needs to be included in the top-level code and the relevant signals exposed by the entity definition are assigned to the signals defined in the top level VHDL.

u9 : entity work.UK101keyboard
port map(
    CLK => clk,
    nRESET => n_reset,
    PS2_CLK => ps2Clk,
    PS2_DATA => ps2Data,
    A => kbRowSel,
    KEYB => kbReadData
);

Additionally, the keyboard row select is latched to the CPU data when the keyboard addresses are selected and written to, as follows:

process (clk)
begin
    if n_kbCS='0' and n_memWR = '0' then
        kbRowSel <= cpuDataOut;
    end if;
end process;

The clock divider

Written in-line within the top-level code because it was not considered complex enough to include as a separate component. The 1MHz CPU clock and the 153600Hz (9600 x 16) serial clock (see later) is created within here. Each divider consists of a counter that is reset once the max value is reached. The counters are triggered on the rising edge of the master (50MHz) clock. For each divider, once the count is less than half-way then the corresponding clock pulse is low, otherwise is high. The CPU and serial frequency is easily changed by altering the max and half-way values for each counter.

process (clk)
begin
    if rising_edge(clk) then
        if cpuClkCount < 49 then
            cpuClkCount <= cpuClkCount + 1;
        else
            cpuClkCount <= (others=>'0');
        end if;
        if cpuClkCount < 25 then
            cpuClock <= '0';
        else
            cpuClock <= '1';
        end if;

-- if serialClkCount < 10416 then -- 300 baud
        if serialClkCount < 325 then -- 9600 baud
            serialClkCount <= serialClkCount + 1;
        else
            serialClkCount <= (others => '0');
        end if;
-- if serialClkCount < 5208 then -- 300 baud
        if serialClkCount < 162 then -- 9600 baud
            serialClock <= '0';
        else
            serialClock <= '1';
        end if;
    end if;
end process;

 

The clock input

On the mini board, there is a 50MHz clock connected to pin 17. This needs to be assigned to the "clk" signal via the assignment editor (see below).


STEP 3: Assign actual pins to the VHDL signals.

Now that the modules have been defined and connected, the relevant signals need to be assigned to physical pins on the device.

All signals that are to be connected to external pins are defined in the top-level VHDL as follows:

entity uk101 is
port(
    n_reset : in std_logic;
    clk : in std_logic;
    rxd : in std_logic;
    txd : out std_logic;
    rts : out std_logic;
    videoSync : out std_logic;
    video : out std_logic;
    ps2Clk : in std_logic;
    ps2Data : in std_logic
);
end uk101;

These are then mapped to actual pins. This can be done using the pin planner, or the "qsf" file can be edited directly.

For the version that I created, I used the following assignments. These were chosen to be compatible with my other FPGA projects. You can, however, use any suitable pin.

The format of the assignment line is
set_location_assignment PIN_pinNumber -to vhdlSignalName

Most (not all) pins support weak pull-ups, so the built-in switch on the board (connected to pin 144) has the pull-up turned on.

So,

set_location_assignment PIN_101 -to rxd
set_location_assignment PIN_103 -to txd
set_location_assignment PIN_104 -to rts
set_location_assignment PIN_17 -to clk
set_location_assignment PIN_144 -to n_reset
set_location_assignment PIN_75 -to video
set_location_assignment PIN_74 -to videoSync
set_location_assignment PIN_87 -to ps2Data
set_location_assignment PIN_86 -to ps2Clk

set_instance_assignment -name WEAK_PULL_UP_RESISTOR ON -to n_reset


STEP 4: Compile the VHDL and upload

Get the VHDL (below) and use the options within the development environment (eg Quartus II) to compile then upload to the FPGA.

JTAG upload is used for a temporary upload of the ".sof" file directly to the FPGA. Use "Active Serial Programming" when uploading the ".pof" to the EPCS4 (or similar) configuration chip for a more permanent programming.

When using JTAG programming, the device load is lost whenever the board is powered down. Using the Active Serial programming, the device remains programmed (via the configuration chip) so that the FPGA is automatically reloaded with the configuration for the UK101 (takes a fraction of a second!) when powered-up.

When compiling you will get many warnings - all can be ignored as they are not relevant to the functioning of this build.


Software and VHDL project download link

By downloading these files you must agree to the following:
The original copyright owners of ROM contents are respectfully acknowledged.
Use of the contents of any file within your own projects is permitted freely, but any publishing of material containing whole or part of any file distributed here, or derived from the work that I have done here will contain an acknowledgement back to myself, Grant Searle, and a link back to this page.
Any file published or distributed that contains all or part of any file from this page must be made available free of charge.


Everything you need to compile the complete project is in the zip file here.
NOTE: UPDATED 26TH JANUARY 2014 - PS2 KEYBOARD PINS CHANGED TO 86 AND 87

Extract to a folder (ensure you keep the folder structure) and open the "uk101.qpf" project in Quartus II version 13sp1.

Included are the VHDL files, HEX file for BASIC (the monitor is already compiled into the VHDL file) and the Quartus project file ready to open.

Also included is the pre-compiled SOF (for JTAG programming) and POF (for active serial programming).


Using the board

The board needs to be connected to the PS/2 keyboard and to the AV input of a monitor or TV. The serial interface is optional but would be needed to transfer and save programs. The serial interface is not needed for initial testing.

Using the assignments that I have mentioned above, the wiring needs to be as follows:

NOTE: UPDATED 26TH JANUARY 2014 - PS2 KEYBOARD PINS CHANGED TO 86 AND 87

With a PS/2 keyboard and display connected, upload the configuration. Immediately when complete you will see the UK101 CEGMON prompt - the device is then immediately ready to use in EXACTLY the same way as the original UK101.

IMPORTANT NOTE ABOUT THE KEYBOARD:
The keyboard has been mapped to match the same layout as the ORIGINAL UK101. As a result, some of the keys to the right of the numbers and letters will not be the same as the keytops on your keyboard. Please refer to a picture of the actual UK101 to see the layout.
Additionally, some of the SHIFTed symbols are different on a PC keyboard compared to the original UK101. Again, the mapping will use those expected on the UK101 original keytops.

eg. SHIFT-8 will give "(" but on a PC keyboard SHIFT-8 is "*"

This behaviour could be changed if needed but requires editing of the UK101keyboard.vhd file.

Note:
CEGMON by default only uses 14 of the available 16 lines. To use the top two lines type the following in BASIC:
POKE 547,12
This sets the top of the text window to the top line - all 16 lines are then used.


Description of the modules used

The text display

The UK101 has a 1K display RAM organised as 16 lines x 64 chars. However, not all character spaces are displayable, and some overlap the HSYNC parts of the screen. This is unusual compared to other micros where every display RAM space is displayable. Practically, about 48 chars per line can be used, the remainder must be blank. The monitor ROM automatically controls this.

I have, however, included blanking within the VHDL to prevent characters that are written outside the displayable screen upsetting the syncs.

The characters occupy an 8x8 matrix but is stretched to 8 pixels horizontal by 16 lines vertical (ie each vertical pixel occupies two lines). To stretch the characters, the vertical line counter is incremented normally, but the actual vertical pixel count does not use the bottom (LSB) bit so that two lines need to be drawn before the next change to the vertical pixel count occurs. This achieves the required double-height character display.

Display parameters used: 5 lines VSYNC, 4.7uS horizontal sync (235 clocks), 64uS per line (3200 clocks) and 313 lines per frame (PAL, no interlace)

The whole process is controlled by the 50MHz clock, triggered on the rising edge of the clock.

process (clk)
begin
    if rising_edge(clk) then
        :
    end if;
end process;

When the horizontal counter reaches 3200 clocks then the count is reset to zero and the vertical count incremented.

To ensure the top of the display is visible, 30 vertical lines are displayed before the characters are sent to the display.

Each character consists of 16 lines and is incremented for every line except for the first display line.

HSYNC is active when the horizontal counter is <235 and VSYNC is active when the vertical line count is <5.

if horizCount < 235 then
    hSync <= '0';
else
    hSync <= '1';
end if;
if vertLineCount < 5 then
    vSync <= '0';
else
    vSync <= '1';
end if;

 These are combined together to create the CSYNC signal.

sync <= hSync and vSync;

The keyboard interface

I intended to write this myself, but someone informed me that Mike Stirling had already done something similar for an implementation of a ZX Spectrum. This code works well, so I modified it for use in the UK101. Full credit to Mike for this code.

Key mappings modified myself, plus the caps-lock key acts as a toggle for the shift-lock toggle key on the original.

The UK101 (and some other micros) assign the keys to a matrix, and one axis on the matrix is connected directly to the address lines. To access a particular row, all address bits that are connected to the keyboard are high except for ONE address bit. The column is then read with any keys pressed connected to that column as a "zero", all others "one". This way, the complete keyboard can be scanned to determine which keys are pressed. The VHDL PS/2 keyboard interface handles this "virtually" and sets the relevant bit in a holding array to 1 or 0 when a key is pressed or released. I will not cover the workings of the PS/2 interface here as other pages already cover it, but the operation is relatively straightforward. Currently the keyboard LEDs are not updated with the shift-lock status. I will do that when I get time.

The PS/2 keyboards that I have tried all work successfully on 3.3V so no need for any level converters. The PS/2 clock and data lines need pull-up resistors so they have been added (the pins that I had spare to connect the PS/2 keyboard did not support on-chip pull-ups).

The serial interface

This is virtually a complete 6850 implementation with the same register bits being used as the original so no software change is required.
The only differences are that the following won't change following any changes via software:
1. The data packet is permanently set to 8 bits, 1 stop bit and no parity.
2. The serial clock is permanently set as clk DIV 16.

The interface that I created is actually an enhancement over the original in that it has a built-in circular receive buffer and automatic hardware handshaking so the baud rate can be increased from the original 300 baud up to 1152000 baud. The baud rate is determined by the tx and rx clock inputs and is set as the clock is 16x the required baud rate. ie. 9600 x 16 = 153600. This approximates to 325 clk (50MHz) pulses per serial clock.

The input buffer is set to 32 bytes. RTS is enabled when 24 or less chars are in the buffer and disabled when 25 or more are in the buffer (ie 8 or less spaces left in the buffer) to allow a 8 byte overrun without losing data. Values can be changed by altering the VHDL.

A state machine is used to determine whether the transmitter part is idle, receiving data bits or receiving the stop bit. The initial input transition (start bit) starts the counter (16 per bit length) and once half-way through the start bit the state changes to receiving data bits. This way the data input is sampled at the mid-point of each bit. During the 8 data bits read the receive buffer is shifted left and the recent bit stored in the LSB. On completion of all 8 bits the incoming register is stored in the receive buffer. Once the stop bit is received  the state is set back to idle.

A state machine is also used to determine whether the transmitter part is idle, sending data bits or sending the stop bit. Once the stop bit is sent the state is set back to idle.

The character and Monitor ROM definitions

To preserve the memory block space for RAM, these two ROMS were defined as large arrays and then the Quartus II compiler with convert them to combinational logic. The significantly increases the compile time but does then allow the complete implementation to fit in the FPGA so is well worth it.

The process code is minimal. Most of the file contains the HEX values for each byte.

type romtable is array (0 to 2047) of std_logic_vector(7 downto 0);
constant romdata : romtable :=
(
    x"5A",x"7E",x"5A",x"18",x"18" .....
);

The process code is triggered on the address, and passes the array value that is pointed to by the address to the data out register.

process (address)
begin
    q <= romdata (to_integer(unsigned(address)));
end process;


Modifications


Modification 1: Using the older monitor instead of CEGMON

I have already converted the old monitor to a table and saved it as a VHDL component to use in the project.
This can be used in place of CEGMON as follows:

Step 1: Include the VHDL file within the project.

I already have done this - it's called MonUK02Rom.VHD

Step 2: Change the instance definition that refers to the CEGMON ROM to use this one instead.

Open the uk101.vhd

Change
u4: entity work.CegmonRom
port map
(
    address => cpuAddress(10 downto 0),
    q => monitorRomData
);

To be
u4: entity work.MonUK02Rom
port map
(
    address => cpuAddress(10 downto 0),
    q => monitorRomData
);

Step 3: Compile

Compile the project then upload the SOF (JTAG) or POF (Active Serial) to the FPGA.

Once uploaded, you will immediately see the "old" monitor text appearing on your screen.
Note: The keyboard handler in the older monitor code was not very good. CEGMON improved it significantly, so I recommend you stick to CEGMON. However, it is included for historical purposes. The old monitor also had a buggy screen scroll - press ENTER several times and you will see the screen occasionally jump up two lines instead of one, additionally, the cursor would sometimes return to the top line. This is exactly as happens on the original. The CEGMON ROM fixes this behaviour.

Note, this is not compatible with the extended screen modification shown later on this page.


Modification 2: Boosting the RAM using one external SRAM chip

The useable RAM for BASIC can be easily increased to 8K, 32K or 40K+ using a single SRAM chip connected to the board.
This requires small changes to the VHDL to connect the pins to the internal 6502 implementation.
These changes show you how you can connect real-world components and interfaces to the internal implementation as if you were connecting it to physical wires and busses.

Implementation

To interface to external SRAM (full 40K+ memory expansion)...

Obtain an SRAM chip that is capable of running at 3.3V - most will.
Get one that is 128k x 8 (ie. 128 KBYTES) - it will have 16 address lines and 8 data lines.
eg. AS6C1008

Step 1: Define what signals are needed between the VHDL signals and the external pins.

This is done in the "port" definition as mentioned earlier in this page.

The new connections that are needed for SRAM are
1. the address bus - 16 lines are needed to address the full 64K memory space (ie. address 0..15)
2. the data bus - 8 data lines needed for the 8-bit bus
3. RAM output enable - one line
4. RAM write enable - one line
5. RAM chip enable/select - one line

So, open the uk101.vhd, pick suitable names for the signal lines and add the new signals to the interface port as shown in red:

entity uk101 is
port(
    sramData : inout std_logic_vector(7 downto 0);
    sramAddress : out std_logic_vector(15 downto 0);
    n_sRamWE : out std_logic;
    n_sRamCS : out std_logic;
    n_sRamOE : out std_logic;


    n_reset : in std_logic;
    clk : in std_logic;
    rxd : in std_logic;
    txd : out std_logic;
    rts : out std_logic;
    videoSync : out std_logic;
    video : out std_logic;
    ps2Clk : in std_logic;
    ps2Data : in std_logic
);
end uk101;

Step 2: Add any new signals

A new "n_memRD" signal was deemed to be necessary, so it was added as shown here (in red, existing code in grey)

   :
signal aciaData : std_logic_vector(7 downto 0);

signal n_memWR : std_logic;


signal n_memRD : std_logic;

signal n_dispRamCS : std_logic;
signal n_ramCS : std_logic;
   :

Step 3: Connect the SRAM port pins to the internal signals as follows

begin
    sramAddress(15 downto 0) <= cpuAddress(15 downto 0);
    sramData <= cpuDataOut when n_WR='0' else (others => 'Z');
    n_sRamWE <= n_memWR;
    n_sRamOE <= n_memRD;
    n_sRamCS <= n_ramCS;
    n_memRD <= not(cpuClock) nand n_WR;


    n_memWR <= not(cpuClock) nand (not n_WR);

    n_dispRamCS <= '0' when cpuAddress(15 downto 10) = "110100" else '1';

In the above code, the sramData lines are connected to the cpuDataOut whenever the CPU write signal is active (zero). When it is not, the SRAM data is set to high-impedance so that the RAM is able to output data and the CPU can then read it using the same pins.

Step 4: Update the chip select code

The RAM will be active for ALL memory spaces not used by other blocks/peripherals, so allocate the chip select when no other selects are active.

So, change the existing internal 4K ram select code
    n_ramCS <= '0' when cpuAddress(15 downto 12)="0000" else '1';
to the new external RAM select
    n_ramCS <= not(n_dispRamCS and n_basRomCS and n_monitorRomCS and n_aciaCS and n_kbCS);

Step 5: Update the bus isolation code to use the SRAM instead of the internal RAM

Change the cpu data select to use the SRAM data input instead of the 4K internal RAM
cpuDataIn <=
    basRomData when n_basRomCS = '0' else
    monitorRomData when n_monitorRomCS = '0' else
    aciaData when n_aciaCS = '0' else
    ramDataOut when n_ramCS = '0' else     < CHANGE TO THE LINE IN RED
    sramData when n_ramCS = '0' else
    dispRamDataOutA when n_dispRamCS = '0' else
    kbReadData when n_kbCS='0'
    else x"FF";

Step 6: Remove old code

The final VHDL change is to remove the old 4K memory, so delete the following lines:

u3: entity work.ProgRam
port map
(
    address => cpuAddress(11 downto 0),
    clock => clk,
    data => cpuDataOut,
    wren => not(n_memWR or n_ramCS),
    q => ramDataOut
);

...that's it, all VHDL changes now complete to use external RAM instead of the internal RAM.

Step 7: Assign physical pins to the new signals

Now that the VHDL is complete, the new signals need to be allocated to physical pins, as was done above. So either use the pin planner utility or edit the uk101.qsf file (quicker) to say which physical pin to use for each signal.

Here are the lines to be added if you keep the same connections as myself. If you look at the picture above, you will see that the assignments match the wiring that I did:

set_location_assignment PIN_4 -to n_sRamWE
set_location_assignment PIN_126 -to n_sRamCS
set_location_assignment PIN_134 -to n_sRamOE
set_location_assignment PIN_8 -to sramAddress[15]
set_location_assignment PIN_30 -to sramAddress[14]
set_location_assignment PIN_24 -to sramAddress[13]
set_location_assignment PIN_28 -to sramAddress[12]
set_location_assignment PIN_136 -to sramAddress[11]
set_location_assignment PIN_132 -to sramAddress[10]
set_location_assignment PIN_139 -to sramAddress[9]
set_location_assignment PIN_142 -to sramAddress[8]
set_location_assignment PIN_143 -to sramAddress[7]
set_location_assignment PIN_141 -to sramAddress[6]
set_location_assignment PIN_137 -to sramAddress[5]
set_location_assignment PIN_133 -to sramAddress[3]
set_location_assignment PIN_135 -to sramAddress[4]
set_location_assignment PIN_129 -to sramAddress[2]
set_location_assignment PIN_125 -to sramAddress[1]
set_location_assignment PIN_121 -to sramAddress[0]
set_location_assignment PIN_122 -to sramData[7]
set_location_assignment PIN_120 -to sramData[6]
set_location_assignment PIN_118 -to sramData[5]
set_location_assignment PIN_114 -to sramData[4]
set_location_assignment PIN_112 -to sramData[3]
set_location_assignment PIN_113 -to sramData[2]
set_location_assignment PIN_115 -to sramData[1]
set_location_assignment PIN_119 -to sramData[0]

Step 8: Connect the SRAM chip to the board

Connect the SRAM (see picture above) to the pin numbers that you have defined (n_sRamCS goes to /CS1 on the SRAM chip).

Other pins connect as follows:
The unused address A16 to GND
CS2 to VCC
Vcc to Vcc,
Gnd to GND

Step 9: Compile and upload.

Compile within the Quartus II suite and upload the VHDL to the board. Once complete you will instantly see the same startup as before. Press C (cold start) then press ENTER for MEMORY SIZE? (will take a few seconds this time) and TERMINAL WIDTH? prompts. You will now see, if successful...

40191 BYTES FREE

...that's it, the UK101 now has a fully expanded memory using just one chip.

 


Modification 3: Extended display - full 64x32 display (PAL)

Before doing this modification, ensure you have implemented and tested modification 2 (more RAM) above, and are still using the CEGMON ROM (ie NOT using modification 1).

Introduction

The original display already has 64 characters per line allocated in memory.

The original has 16 lines (max) displayable, so 16x64 = 1024bytes (1K) is used for the display regardless of how much is actually visible on the screen.

The original UK101 required the CPU and display clocks to be synchronised, so that dictated the "pixel clock" that could be used for the display. With the FPGA version, however, dual-port RAM is used for the display so the access speed needed for the display is totally independent to the CPU speed.

The aim of this sub-project is to change the existing display from showing part of the 64 chars per line (exactly as the original UK101 does) to utilising all 64 bytes on the display line. Additionally, by doubling the amount of display RAM (2K instead of 1K) the number of lines can also be doubled. The existing display on the UK101 uses two scanlines for every vertical character pixel, so doubling the number of lines will still show every pixel that was on the original.

Additionally, the extended display still uses exactly the same memory map (for the top half of the screen) as the original, so all games will also work (such as space invaders) - the only difference is that they will occupy a smaller part of the screen.

The extended screen makes a huge difference when using BASIC programming as there are now 2048 displayable characters on the screen at any time instead of 768 (48x16).

CEGMON already has capability of using different size screens and has startup-values assuming 48 displayable characters on 14 (or 16) displayable lines. These can be changed by issuing POKE commands in BASIC (this is how CEGMON achieves windowing) but this change will also patch the default startup values (still can be changed with POKEs if needed) so that the full display is used at switch-on.

So, the steps needed to make the larger screen are as follows:

1. Allocate 2K of RAM to the screen instead of 1K. This is possible as there is a gap in the existing memory map, so this achieves 100% compatibility with existing program because the screen start in memory is unchanged.

2. Alter the display to pick up the additional RAM. This involves adding one more address line to the address bus used for the display. 1K needs 10 address lines (A0..A9), so 2K needs 11 address lines (ie. A10 in addition).

3. The display needs to draw the characters thinner to get all 64 on the screen. This is done by adjusting the pixel clock to run faster, so the characters are drawn quicker while the scan is proceeding, allowing more to fit on the screen. The existing screen has each line with 12 blanks before it - some of these run over the sync signal ( ! ). The start/end timing values are therefore adjusted to centre the 64 characters on the screen.

4. The vertical height of the characters to be reduced. The existing UK101 displays all characters double-height (ie. each character occupies 16 scanlines). A small change is needed to display them "normal" height (8 scanlines).

5. Finally, the CEGMON monitor is patched to assume the full 64x32 display is used at startup. There are a small set of locations in the ROM that need altering. These can either be changed by editing the HEX bytes directly or, as I will show here, use VHDL to patch them. I prefer VHDL patching for small data changes because the original HEX remains untouched, and it is then possible to control the patch via a signal so that the patch can be switched on of off as needed.

These are shown in detail below...

Implementation

Now that the summary of changes has been discussed, here is exactly how to do it in a step-by-step approach. Follow these steps exactly and you will not have any problems...

Step 1: Allocate 2K of RAM to the screen instead of 1K.

The address range of the RAM now uses 11 bits (A0...A10) instead of 10 bits (A0...A9) and consists of 2048 bytes instead of 1024 bytes. The VHD that defines the display RAM needs updating to use the new values. If you are familiar with the Quartus wizard windows then you can use those to change the RAM size, otherwise, you can edit the VHD directly as shown below.

Open "DisplayRam.vhd" and make the following changes:

ENTITY DisplayRam IS
PORT
(
address_a : IN STD_LOGIC_VECTOR
(10 DOWNTO 0);
address_b : IN STD_LOGIC_VECTOR (10 DOWNTO 0);
clock : IN STD_LOGIC := '1';
data_a : IN STD_LOGIC_VECTOR (7 DOWNTO 0);
data_b : IN STD_LOGIC_VECTOR (7 DOWNTO 0);
wren_a : IN STD_LOGIC := '0';
wren_b : IN STD_LOGIC := '0';
q_a : OUT STD_LOGIC_VECTOR (7 DOWNTO 0);
q_b : OUT STD_LOGIC_VECTOR (7 DOWNTO 0)
);
END DisplayRam;
:

COMPONENT altsyncram
GENERIC (
   :
);
PORT (
:
address_b : IN STD_LOGIC_VECTOR
(10 DOWNTO 0);
:
address_a : IN STD_LOGIC_VECTOR (10 DOWNTO 0);
:);

altsyncram_component : altsyncram
GENERIC MAP (
:
numwords_a =>
2048,
numwords_b => 2048,
:
widthad_a =>
11,
widthad_b => 11,
:

Step 2: Alter the display and CPU connections to pick up the additional display RAM.

The NEW memory map is the same as the original, but the display now occupues 2K (D000-D7FF) instead of 1K (D000-D3FF). Since A10 is now used to address internal RAM within the display, it is no longer to be used as part of the required chip select bits.

Name Address range Required address bits
Program RAM (4K) 0000-0FFF 0000xxxx xxxxxxxx
BASIC ROM (8K) A000-BFFF 101xxxxx xxxxxxxx
Display RAM (2K) D000-D7FF 11010xxx xxxxxxxx
Keyboard DC00-DFFF 110111xx xxxxxxxx
ACIA (serial/cassette) F000-F001 11110000 0000000x
Monitor ROM (2K) F800-FFFF 11111xxx xxxxxxxx

Open uk101.vhd

Change the following...

u8: entity work.DisplayRam
port map
(
address_a => cpuAddress
(10 downto 0),
address_b => dispAddrB,
clock => clk,
data_a => cpuDataOut,
data_b => (others => '0'),
wren_a => not(n_memWR or n_dispRamCS),
wren_b => '0',
q_a => dispRamDataOutA,
q_b => dispRamDataOutB
);

Change the size of the dispAddrB bus definition to increase it to 11 bits...

signal dispAddrB : std_logic_vector(10 downto 0);

Also, the display RAM select bits have altered slightly (see above memory map table), so make the corresponding change...

n_dispRamCS <= '0' when cpuAddress(15 downto 11) = "11010" else '1';

Open UK101TextDisplay.vhd

Change the following...

port (
charAddr : out std_LOGIC_VECTOR(10 downto 0);
charData : in std_LOGIC_VECTOR(7 downto 0);
dispAddr : out std_LOGIC_VECTOR
(10 downto 0);
dispData : in std_LOGIC_VECTOR(7 downto 0);
clk : in std_logic;
video : out std_logic;
sync : out std_logic
);

The line scan line counter now only counts 8 lines (0..7) and the character line vertical counter now counts 32 lines (0..31) so the signal sizes need to be altered...

signal charVert: STD_LOGIC_VECTOR(4 DOWNTO 0);
signal charScanLine: STD_LOGIC_VECTOR(2 DOWNTO 0);

To make all 64 characters appear centered on the screen we now need to alter the horizontal timing threshold values for start and end of each character line (for info, there is a total of 3200 x 50MHz clocks for each line). Make the following change...
if (horizCount < 780) or (horizCount > 2830) then

So, 780 x 50MHz clocks from start of sync signal to start of characters. Display complete when 2830 x 50MHz clocks have occured from the start of sync. There are now 4 clocks per pixel, 64 chars, 8 pixels per char, so turn off point 2830 = 781 (start pixel pos) + 64 x 8 x 4 (64 chars) + 4 (end pixel) +1 (to completion of end pixel)

Step 3: The display needs to draw the characters thinner to get all 64 on the screen.

As mentioned above, there are now 4 clocks per pixel instead of the original 6

So, again in UK101TextDisplay.vhd, change the following line to count 0 to 3 instead of 0 to 5 ...

if pixelClockCount < 3 then
pixelClockCount <= pixelClockCount+1;
else
:
end if;

Step 4: The vertical height of the characters to be reduced.

The character address now deals with every scanline (not every other) so alter the characterAddress definition in UK101TextDisplay.vhd...

charAddr <= dispData & charScanLine;

Also change the scan line count reset line value

if charScanLine = 7 then
    charScanLine <= (others => '0');
    charVert <= charVert+1;

Step 5: Auto-patching the CEGMON monitor ROM

The above set of changes is all that is needed to produce the display. However, CEGMON is currently set to display 48 characters on 16 lines. The values that are in the ROM are patched so that the larger range is used as default.  As mentioned above, the CEGON hex values can be altered directly. However, future implementations may have the option to switch screen resolutions back to the original (via a signal/pin) so I didn't want to permanently change the original ROM dump. Therefore, I have implemented a simple patching mechanism that provides alternative values for the required locations.

This is done in uk101.vhd by adding the following lines:
cpuDataIn <=
    -- CEGMON PATCH FOR 64x32 SCREEN
    x"3F" when cpuAddress = x"FBBC" else -- CEGMON SWIDTH (was $47)
    x"00" when cpuAddress = x"FBBD" else
-- CEGMON TOP L (was $0C (1st line) or $8C (3rd line))
    x"BF" when cpuAddress = x"FBBF" else
-- CEGMON BASE L (was $CC)
    x"D7" when cpuAddress = x"FBC0" else
-- CEGMON BASE H (was $D3)
    x"00" when cpuAddress = x"FBC2" else
-- CEGMON STARTUP TOP L (was $0C (1st line) or $8C (3rd line))
    x"00" when cpuAddress = x"FBC5" else
-- CEGMON STARTUP TOP L (was $0C (1st line) or $8C (3rd line))
    x"00" when cpuAddress = x"FBCB" else
-- CEGMON STARTUP TOP L (was $0C (1st line) or $8C (3rd line))
    x"10" when cpuAddress = x"FE62" else
-- CEGMON CLR SCREEN SIZE (was $08)
    x"D8" when cpuAddress = x"FB8B" else
-- CEGMON SCREEN BOTTOM H (was $D4) - Part of CTRL-F code
    x"D7" when cpuAddress = x"FE3B" else
-- CEGMON SCREEN BOTTOM H - 1 (was $D3) - Part of CTRL-A code

    basRomData when n_basRomCS = '0' else
    monitorRomData when n_monitorRomCS = '0' else
    aciaData when n_aciaCS = '0' else
    sramData when n_ramCS = '0' else
    dispRamDataOutA when n_dispRamCS = '0' else
    kbReadData when n_kbCS='0'
    else x"FF";

As you can see, these patches must appear before the others to ensure the hard-coded alternatives are passed to the CPU in preference to the original values.

That's all modifications complete.

Step 6: Compile and upload.

Compile within the Quartus II suite and upload the VHDL to the board. Once complete you will instantly see the same startup as before but in a smaller font at the very top of the screen. You now have a 32 line screen with 64 characters per line.


Modification 4: Fast CPU selection

Within the UK101Keyboard component I have made available additional signals that are controlled by the 12 FN keys on the top of the keyboard.

One set of the signals - FNkeys(1) to FNKeys(12) are 0 when the FN key is released and 1 when the FN key is pressed.

The second set of signals FNtoggledKeys(1) to FNtoggledKeys(12) are 0 when the board powers-up then flip states each time that FN key is pressed.

In this modification I use FNtoggledKeys(1) as the "fastMode" signal to select slow and fast CPU speeds. On startup, FNtoggledKeys(1) is 0. Pressing it once chages to 1, and pressing again changes to 0 etc... The fastMode signal that this is connected to is used within the clock divider in the top level VHDL to determine what the final CPU speed will be.

In the code change I show here, 0 (power-up default) will set the CPU to 1MHz (standard UK101 speed). Pressing the F1 key will switch it to 12.5MHz ( ! ) and pressing F1 again will switch it back.

Note that CEGMON has a keyboard auto-repeat that occurs if a key is held for a certain time. When in high-speed mode, this timing loop is also speeded up, so you would need to press and release a key very quickly to prevent multiple occurrences appearing on the screen. The CEGMON autorepeat preset values are patched to correct autorepeat timeout/rate only when fast mode is active. Details of this is shown below.

If you find that the fast mode doesn't work and you are using external RAM then it is likely that either your RAM is not fast enough or the wires to the RAM chip are too long. Altering the values that I show below to reduce the fast speed would help - experiment to find the highest CPU speed that works.

Step 1: Assign F1 to be the "fast" button

Open uk101.vhd

Add a fastMode signal

    :
    signal kbRowSel : std_logic_vector(7 downto 0);

    signal fastMode : std_logic;
begin
    :

Connect this signal to the FNtoggledKeys(1) connection on the UK101keyboard component

u9 : entity work.UK101keyboard
port map(
   
FNtoggledKeys(1) => fastMode ,
    CLK => clk,
    nRESET => n_reset,
    PS2_CLK => ps2Clk,
    PS2_DATA => ps2Data,
    A => kbRowSel,
    KEYB => kbReadData
);

Step 2: Control the clock divider using the new signal

Finally, update the clock divider to switch between two sets of divider speeds depending on whether "fastMode" is 0 (normal) or 1 (fast). Add the lines in red. Value used will give a fast CPU speed of 10MHz. You can try increasing to other frequencies as shown in the VHDL comment below. Note: 25MHz needs very fast RAM and short connections and is unlikely to work with the RAM used in modification 3. The 16.6MHz speed works fine for me with external SRAM. 25MHz speed works with internal RAM. If it crashes when running fast, use values for a slower speed.

    :
process (clk)
begin
    if rising_edge(clk) then
        if fastMode = '0' then -- 1MHz CPU clock
            if cpuClkCount < 49 then
                cpuClkCount <= cpuClkCount + 1;
            else
                cpuClkCount <= (others=>'0');
            end if;
            if cpuClkCount < 25 then
                cpuClock <= '0';
            else
                cpuClock <= '1';
            end if;
        else
            if cpuClkCount < 4 then
-- 4 = 10MHz, 3 = 12.5MHz, 2=16.6MHz, 1=25MHz
                cpuClkCount <= cpuClkCount + 1;
            else
                cpuClkCount <= (others=>'0');
            end if;
            if cpuClkCount < 2 then
-- 2 when 10MHz, 2 when 12.5MHz, 2 when 16.6MHz, 1 when 25MHz
                cpuClock <= '0';
            else
                cpuClock <= '1';
            end if;
        end if;

            :

Step 4: Patch to correct CEGMON's autorepeat when on fast speed.

The CEGMON key autorepeat would also start and run much faster when in fast mode, making typing virtually impossible. Several ways can be done to patch the ROM. What I did was to patch the delay loop start value. The patch is applied exactly the same way as for the CEGMON patch for the larger display. The code below also shows how conditional patching can be done.

In the uk101.vhd, add a patch line to the CPU data read, as follows:

cpuDataIn <=
    -- CEGMON PATCH TO CORRECT AUTO-REPEAT IN FAST MODE
    x"F0" when (cpuAddress & fastMode)= "11111100111000001" else -- Address = FCE0 and fastMode = 1 : CHANGE REPEAT RATE LOOP VALUE (was $10)
    -- CEGMON PATCH FOR 64x32 SCREEN
   
x"3F" when cpuAddress = x"FBBC" else -- CEGMON SWIDTH (was $47)
   
x"00" when cpuAddress = x"FBBD" else -- CEGMON TOP L (was $0C (1st line) or $8C (3rd line))
   
x"BF" when cpuAddress = x"FBBF" else -- CEGMON BASE L (was $CC)
   
x"D7" when cpuAddress = x"FBC0" else -- CEGMON BASE H (was $D3)
   
x"00" when cpuAddress = x"FBC2" else -- CEGMON STARTUP TOP L (was $0C (1st line) or $8C (3rd line))
   
x"00" when cpuAddress = x"FBC5" else -- CEGMON STARTUP TOP L (was $0C (1st line) or $8C (3rd line))
   
x"00" when cpuAddress = x"FBCB" else -- CEGMON STARTUP TOP L (was $0C (1st line) or $8C (3rd line))
   
x"10" when cpuAddress = x"FE62" else -- CEGMON CLR SCREEN SIZE (was $08)
   
x"D8" when cpuAddress = x"FB8B" else -- CEGMON SCREEN BOTTOM H (was $D4) - Part of CTRL-F code
   
x"D7" when cpuAddress = x"FE3B" else -- CEGMON SCREEN BOTTOM H - 1 (was $D3) - Part of CTRL-A code
   
basRomData when n_basRomCS = '0' else
   
monitorRomData when n_monitorRomCS = '0' else
   
aciaData when n_aciaCS = '0' else
   
sramData when n_ramCS = '0' else
   
dispRamDataOutA when n_dispRamCS = '0' else
   
kbReadData when n_kbCS='0'
   
else x"FF";
 

Step 5: Compile and upload.

Compile within the Quartus II suite and upload the VHDL to the board. Once complete you will instantly see the same startup as before. Try a simple loop in BASIC and run it. While running, press F1 - you should see a huge improvement in speed. Press F1 again to return it to the original speed.

Note: the external SRAM is the limiter on the maximum speed. Using internal RAM only allows 25MHz operation.


Loading and saving programs

The original UK101 used a cassette interface which was actually circuitry that converts the audio stored on the cassette to a digital form which is then passed directly into the 6850 ACIA.

The UK101 would interface to the serial (ACIA) and not know if there is a serial interface or cassette interface connected to it. The original UK101 has suitable jumpers so that the cassette interface can be bypassed and the serial connections used instead. This FPGA board uses that serial configuration.

The FPGA has implemented the 6850 serial port but has not implemented the cassette hardware although you could build a small circuit it needed, the same as the original UK101.

So, instead of using cassettes to save or load it uses a terminal session on a PC (Windows, Linux, MacOS etc) instead.

Please refer to the original UK101 manual on details as to how to save or load. This board is IDENTICAL except that you would transfer/paste a file to this board via a terminal session instead of playing a cassette. Similarly, you would capture text appearing on the terminal instead of recording to a cassette.

Searching the net will find some software, but not all files have the proper carriage return/line feeds on it. Make sure it has to allow the file to load properly.

All existing UK101 software would work exactly as the original. Various software tested includes Space Invaders, 8K Super Invaders, Nim, Golf, etc. etc.

Loading (playing back) a program within BASIC

Connect the board to a 3.3V compatible serial interface (eg. a 3.3V to USB serial converter). You MUST have an interface that has a CTS connection because hardware handshaking is needed to avoid losing characters.

Start a terminal session on the connected PC with the following parameters:

9600 Baud, 8 bits, 1 stop bit, no parity, hardware (RTS/CTS) handshaking.

Type LOAD and press ENTER

The UK101 board now accepts all input (and commands) from the serial.

Paste/copy the program being loaded into the terminal. It will be transferred to the UK101.

Then (from the manual)... "Pressing SPACE and the RETURN returns to BASIC and the newly loaded program is resident for listing (type LIST and press RETURN) or running (type RUN and press RETURN) etc."

Saving (recording) a program

Set up the serial interface the same way as for loading.

Based on text from the original UK101 manual...

Type SAVE (and press RETURN).

Type LIST but do not pres RETURN.

As soon as RETURN is pressed, the LIST function simply lists the program onto the serial port (or cassette).

Start the terminal capture to a text file on the PC.

Press RETURN on the UK101 board.

When complete, stop the terminal capture on the PC.

On the UK101 board, press SPACE and RETURN.

 


My other pages

CLICK HERE TO GO TO MY MAIN PAGE FOR MY OTHER PROJECTS


I hope this page has been useful.

Grant.

To contact me, my current eMail address can be found here. Please note that this address may change to avoid spam.

Note: All information shown here is supplied "as is" with no warranty whatsoever, however, please let me know if there are any errors. All copyrights recognised.