In this blog I share my observations, thoughts and experience about computers, linguistics, philosophy and many other things that interest me.

Showing posts with label GMS/359. Show all posts
Showing posts with label GMS/359. Show all posts

Wednesday, January 21, 2026

GMS 2950: A Cryptographic Coprocessor Joins the Mainframe

Today marks a significant milestone for the GMS/359 project: the successful integration and verification of the GMS 2950 Cryptographic Processor — a modular exponentiation accelerator connected to our Multiplexor Channel.

The Achievement

GMS 2950 Crypto Test
====================
Test: 3^5 mod 7 = ?
Computing...
SUCCESS! Z = 0x05

That humble 0x05 represents something remarkable: our little FPGA mainframe just computed its first cryptographic operation. The number 243 (which is 3⁵) modulo 7 equals 5 — verified in hardware, orchestrated through authentic System/360-style Channel I/O.

Why Modular Exponentiation?

The operation z = y^x mod m is the mathematical foundation of modern public-key cryptography:

  • RSA encryption and decryption
  • Diffie-Hellman key exchange
  • Digital signatures

IBM didn't add dedicated crypto acceleration to their mainframes until the z990 in 2003. Our 1965-era architecture recreation now has capabilities that Big Iron took four decades to acquire!

Architecture

The GMS 2950 connects to the Multiplexor Channel as device 0x2A:

┌─────────────────────────────────────────────────────┐
│            2870 MULTIPLEXOR CHANNEL                 │
├─────────────────────────────────────────────────────┤
│  10h Video Controller     ──── VGA Output           │
│  11h Keyboard Controller  ──── PS/2 Input           │
│  12h UART                 ──── Serial Console       │
│  2Ah Crypto Processor     ──── NEW!                 │
│  2Bh SYSINFO              ──── System Information   │
│  2Eh Console              ──── Console Area         │
└─────────────────────────────────────────────────────┘

The programming model follows our Channel I/O conventions:

WRITE 25 bytes to load operands and start computation:

Bytes 0-7:   X (exponent)   - 64-bit little-endian
Bytes 8-15:  Y (base)       - 64-bit little-endian  
Bytes 16-23: M (modulus)    - 64-bit little-endian
Byte 24:     CONTROL        - bit 0 = START

READ 8 bytes to retrieve the result:

Bytes 0-7:   Z (result)     - 64-bit little-endian

The beauty of Channel I/O shines here: the CPU issues SIO 02Ah, and the channel handles all the byte-by-byte transfers autonomously. The CPU can poll with TIO or (eventually) receive an interrupt when computation completes.

The Test Program

; Load operands and start crypto
        LFI     R1, crypto_write_ccw
        ST      [tMSVA.CAW], R1
        SIO     02Ah                    ; Start WRITE to crypto

poll_write:
        TIO     02Ah
        BB      poll_write              ; Wait for completion

; ... computation happens in hardware ...

; Read result
        LFI     R1, crypto_read_ccw
        ST      [tMSVA.CAW], R1
        SIO     02Ah                    ; Start READ from crypto

poll_read:
        TIO     02Ah
        BB      poll_read

; Check result
        LFI     R2, result_buffer
        LB      R3, [R2]                ; Load first byte of result
        LFI     R4, 5                   ; Expected value
        CMP     R3, R4
        BNE     test_failed          ; Branch if not equal

        ; SUCCESS!

The CCW (Channel Command Word) setup is straightforward:

crypto_write_ccw:
        DB      01h                     ; WRITE command
        DB      00h                     ; No flags
        DW      25                      ; 25 bytes
        DD      crypto_operands         ; Source address

crypto_operands:
        DB      05h, 00h, 00h, 00h, 00h, 00h, 00h, 00h  ; X = 5
        DB      03h, 00h, 00h, 00h, 00h, 00h, 00h, 00h  ; Y = 3
        DB      07h, 00h, 00h, 00h, 00h, 00h, 00h, 00h  ; M = 7
        DB      01h                                      ; START

Implementation Details

The crypto core implements the classic square-and-multiply algorithm for modular exponentiation, built from three layers:

  1. modm_adder — computes (x + y) mod m in 3 pipeline stages
  2. modm_multiplier — computes (x × y) mod m using repeated addition
  3. modm_exponentiation — computes y^x mod m using square-and-multiply

For 64-bit operands, a full exponentiation takes approximately 30,000 clock cycles — about 2.4ms at our 12.5 MHz system clock. Imperceptible to humans, but the CPU is free to do other work (or service other channel programs) during computation.

The design is parameterized: changing two generic constants scales it to 128-bit or 256-bit operands. The tradeoff is FPGA resources and computation time, both of which scale quadratically with bit width.

Modular Design

The channel controller now supports compile-time configuration:

entity gms_2870_multiplexor_channel is
    generic (
        ENABLE_CRYPTO : boolean := false
    );

When ENABLE_CRYPTO is false, all crypto-related logic is optimized away by the synthesizer. Our Makefile selects the appropriate top-level:

ifeq ($(WITH_CRYPTO),1)
    TOP_FILE = rtl/gms359_top_crypto.vhd
else
    TOP_FILE = rtl/gms359_top.vhd
endif

Building with or without crypto is now a simple make WITH_CRYPTO=1.

CPU Enhancements

Testing the crypto processor also drove expansion of the GMS 2050 instruction set. New instructions added today:

Opcode Mnemonic Description
0x19 CR Compare Register
0x1B SR Subtract Register
0x14 NR AND Register
0x16 OR OR Register
0x17 XR XOR Register
0x88 SHR Shift Right Logical
0x89 SHL Shift Left Logical
0x8A SAR Shift Right Arithmetic

The assembler now supports "smart" mnemonics that automatically select RR (register-register) or RX (register-memory) format based on operands. LD R4, R3 assembles to LR, while LD R4, [R5+100] assembles to L.

Resource Utilization

Adding the crypto processor and new CPU instructions had a noticeable impact:

Metric Before After
Wires ~100K 251K
Fmax ~33 MHz 21.75 MHz
Build time   1× 4×

The maximum clock frequency dropped significantly, primarily due to the shift instructions (barrel shifter logic) and the 64-bit crypto datapath. However, we still have 73% timing margin over our 12.56 MHz target — plenty of headroom.

What's Next

The GMS 2950 opens up interesting possibilities:

  • Larger key sizes — scale to 256-bit for real-world crypto
  • RSA implementation — full encrypt/decrypt in software using the accelerator
  • Performance measurement — compare against pure software implementation
  • Integration with the Pico 2 — the planned GMS 2350 RISC-V accelerator could work alongside the crypto unit

Closing Thoughts

There's something deeply satisfying about watching a recreation of 1960s mainframe architecture perform modern cryptographic operations. The GateMate A1 FPGA, drawing a few hundred milliwatts from a USB port, now does what would have been unimaginable to the engineers who designed the original System/360.

The Channel I/O model proves its elegance once again: adding a complex coprocessor required no changes to the CPU. The channel handles all communication; the CPU just says "start I/O to device 2A" and waits for completion. This is the architecture that ran the world's banking systems, airline reservations, and scientific computing for decades — and it's still teaching us lessons about clean system design.

3⁵ mod 7 = 5

A small calculation. A big step for GMS/359.


The GMS/359 project recreates IBM System/360 architecture on modern FPGA hardware. Source code will be available at gitlab.com/gatemate/s359.

Tuesday, January 20, 2026

asm359 v0.4 Released: Smart Instructions, Shifts, and Bug Fixes

Version 0.4 of asm359, the assembler for the GateMate System/359 project, is now available. This release brings several usability improvements, new instruction formats, and important bug fixes that were discovered during real-world testing.

Smart Instructions

The most visible addition is a set of "smart" instructions that automatically select the appropriate encoding based on operand types. Instead of choosing between LR (register-register) and L (register-memory) manually, you can now simply write LD and let the assembler figure it out:

LD  R1, R2          ; Assembles as LR (2 bytes)
LD  R1, [R11+100h]  ; Assembles as L  (4 bytes)

This pattern applies to seven common operations: LD, ADD, SUB, AND, OR, XOR, and CMP. The assembler examines the second operand—if it's a register, it emits the compact RR-format; if it's a memory reference in brackets, it emits the RX-format. This reduces cognitive load when writing code and makes the source more readable.

Shift Instructions

GMS/359 now has proper shift instructions: SHR (logical right), SHL (logical left), and SAR (arithmetic right). These use a simple 4-byte format with the shift amount encoded directly in the instruction:

SHR R4, 4           ; Shift R4 right by 4 bits
SHL R2, 8           ; Shift R2 left by 8 bits
SAR R3, 1           ; Arithmetic shift right by 1

The shift amount is limited to 0-31, which covers the useful range for 32-bit registers.

Branch Instruction Changes

A deliberate breaking change in this release: the raw BC mask, address syntax has been removed. The S/360-style explicit condition masks were error-prone—it's too easy to confuse which bit means what. Instead, the assembler now requires mnemonic aliases:

; Old (no longer accepted):
BC  4, poll_loop
BC  7, error_handler    ; Wait, is 7 "not equal" or "no overflow"?

; New (required):
BB  poll_loop           ; Branch if Busy (CC=2)
BNE error_handler       ; Branch if Not Equal (CC≠0)

This release also adds BB (Branch if Busy) and BNB (Branch if Not Busy) for I/O polling loops, which are common in GMS/359 code.

Expression Handling Fixes

Two related bugs in expression parsing were fixed. The assembler now correctly handles symbol arithmetic like:

header_ccw:
    DB      01h, 00h
    DW      header_end - header_msg   ; Calculate string length
    DD      header_msg

Previously, symbol - symbol expressions either failed outright or—worse—generated incorrect relocations. The fix involved two parts: first, actually parsing symbol names after the minus sign (the code had a comment saying "keep it simple for now" and just gave up), and second, recognizing that when both symbols are in the same segment, their difference is an absolute constant that needs no relocation.

Accurate Error Reporting

A subtle but important fix: error messages now report the correct source line when using %include directives. Previously, the assembler maintained its own line counter that included lines from all files, leading to confusing messages like "line 51: unknown instruction" when the actual problem was on line 83. The fix was straightforward—use the preprocessor's source location tracking instead of an independent counter.

Code Reorganization

Behind the scenes, the main assembler module was split into two files. The original assemble.c had grown to over 2,400 lines, mixing directive processing with instruction encoding. It's now divided into assemble.c (core state management, emit functions, directive processing) and asm_insns.c (GMS/359 instruction parsing and encoding). Each file is around 1,100-1,400 lines—still substantial, but more manageable.

What's Next

With the assembler toolchain stabilizing, focus shifts back to the hardware side. The shift instructions need to be implemented in the GateMate FPGA, and there's ongoing work on the cryptographic coprocessor (the test program that found several of these bugs is a modular exponentiation test for the GMS 2950).

The full source is available in the project repository. As always, bug reports and feedback are welcome.


asm359 is part of the GateMate System/359 project—a modern FPGA-based system inspired by the IBM System/360 architecture, but deliberately not binary compatible. The goal is to preserve the elegance of S/360's design while eliminating historical quirks and adapting to contemporary hardware.

Monday, January 19, 2026

Eight Days, Sixty Years: Building a System That Knows What It's For

Eight days ago, I asked Claude a question: "What's the most optimal organization for a microprogrammed CPU?"

Today, I have a computing system that displays its own name on a terminal.

GMS/359
GMS 2050 CPU
GateMate A1

This is the story of those eight days. But more importantly, it's a story about why the IBM System/360 — a machine designed in 1964 — still matters in 2026. And why I chose to build one on an FPGA.

The Machine Room

Look at this picture. It's from Jason, a beautiful S/360 emulator by Camiel Vanderhoeven (of "Operation Blinkenlights" fame). I first saw it about ten years ago. At the time, it was just a pretty picture of old computers.

Now, after eight days of building my own S/360, every element suddenly makes sense:

  • CPU — the central processing unit with its operator panel
  • DSP at 0C0 — the display console
  • CON at 01F — the operator console
  • PRT 1403 at 01E — the legendary line printer
  • RDR 2501 at 00C — the card reader
  • DASD at 350 — Direct Access Storage Device (disk)
  • TAPE at 280 — the magnetic tape drives

Those numbers — 0C0, 01F, 01E, 00C, 350, 280 — are device addresses. The same kind of addresses I now type into SIO instructions in my own code:

SIO   010h        ; Start I/O on video controller
SIO   011h        ; Start I/O on keyboard
SIO   012h        ; Start I/O on UART
SIO   02Bh        ; Start I/O on SYSINFO

There's no better way to understand something than to build it yourself.

What We Built in Eight Days

On January 10th, I had questions about CPU architecture. By January 18th, I had:

GMS 2050 CPU:

  • 16 general-purpose registers (32-bit)
  • 12 working instructions: LR, AR, LFI, LB, SB, ST, BC, SIO, TIO, LPSW, MVI, NOPR
  • 24-bit addressing (16 MB address space)
  • Base register addressing with NASM-style syntax
  • Condition code handling

GMS 2870 Multiplexor Channel:

  • Full Channel Command Word (CCW) execution
  • READ and WRITE operations
  • Command chaining
  • Channel Status Word (CSW) reporting

Peripheral Devices:

  • Video controller (0x10) — text output to VGA
  • PS/2 Keyboard (0x11) — with FIFO buffer
  • UART (0x12) — 115200 baud serial
  • SYSINFO (0x2B) — 256-byte system identification ROM
  • Console (0x2E) — debug output

Development Tools (asm359):

  • Assembler with NASM-style preprocessor
  • Linker for multi-section programs
  • IPL loader via UART

The keyboard works. I press 'A', I see 1C F0 1C on the UART — the PS/2 make and break codes, converted to hex by a lookup table that reminded me of ENIAC's glowing decimal displays from 1946. I press Pause, I see the legendary eight-byte monster: E1 14 77 E1 F0 14 F0 77.

The system can identify itself. Device 2Bh returns a 256-byte ROM containing the magic string "GMS/359", the CPU model, the FPGA type, feature flags, memory size, clock frequency. The machine knows what it is.

Why System/360?

Here's what I want to explain, and I hope I can do it justice.

When I first learned about the IBM System/360, it was through the history of UNIX — the porting efforts, the Interdata 7/32, and later the S/360 ports. Then I read Emerson Pugh's books on IBM history, and Fred Brooks' "The Mythical Man-Month." I fell in love with this machine.

Not because it's old. Not because it's "retro." Not because vacuum tubes and blinking lights are aesthetically pleasing (though they are).

I fell in love because the System/360 was designed, from its very first day, as a serious system.

Let me explain what I mean.

The Problem State

In S/360 architecture, the CPU has two modes: supervisor state and problem state. The operating system runs in supervisor state. User programs run in problem state.

Think about that name: problem state. Not "user mode." Not "application mode." Not "sandbox" or "container" or whatever the fashionable term is today.

Problem state. Because the machine exists to solve problems. Customer problems. Business problems. The problems that banks and insurance companies and airlines and governments need solved reliably, day after day, year after year, decade after decade.

The System/360 was not designed to make "Hello World" easy. When you first look at Channel I/O, you might think: "This is absurdly complicated! I need to set up a Channel Address Word, create a Channel Command Word chain, execute SIO, poll with TIO, check the Channel Status Word... just to print a string?"

Yes. Exactly.

Because the System/360 was never meant for printing one string. It was meant for printing millions of paychecks, processing billions of transactions, running the infrastructure of civilization. The "complexity" of Channel I/O is precisely what makes it powerful: DMA transfers, command chaining, device independence, error recovery, concurrent I/O across multiple channels. The architecture pays its complexity cost once, then amortizes it across fifty years of real work.

The Difference

Compare this to so many modern projects. You know the pattern:

"Here's our new CPU/board/platform! Look how easy it is to blink an LED! Look, Hello World in just three lines! Join our Discord! Buy our dev kit!"

And then... they disappear. The company pivots. The community fragments. The documentation rots. Your code stops working. You move on to the next shiny thing.

The System/360 architecture, announced in 1964, is still running bank transactions today. The same instruction set. The same Channel I/O concepts. The same principles. Sixty years and counting.

That's not an accident. That's what happens when you design a system for real work instead of easy demos.

IBM didn't optimize for the first five minutes of the user experience. They optimized for the next fifty years of the customer's business.

Why I'm Building This

The GMS/359 is not going to replace modern mainframes. It's not going to run z/OS. It's a hobby project on a small FPGA, built for the joy of understanding.

But I'm building it because I believe in the philosophy behind it.

When I design the Channel I/O system, I'm not thinking "how can I make Hello World easier?" I'm thinking "how can I make this architecture correct?" How can I build something that could, in principle, scale? Something where the complexity exists for good reasons, not just accumulated cruft?

When I added the SYSINFO device (0x2B), it wasn't because I needed it for a demo. It's because a real system should be able to identify itself. A real system should have feature flags that software can query. A real system should know its own version number.

The System/360 taught me this: design for the real work, not the demo.

The Next Fifty Years (Or At Least the Next Few Months)

Version 0.1.0 is tagged and pushed. The repository is now public at gitlab.com/gatemate/s359.

What's next:

  • More instructions (SR, logical operations, shifts)
  • Selector Channel for block devices, PSRAM as "RAM-disk"
  • Interrupt handling
  • IPL loads the "nucleus" and the "RAM-disk" image (created as a "DASD", perhaps), and the nucleus starts

But more importantly: continuing to learn from an architecture that got so many things right, six decades ago.

Sunday, January 18, 2026

SERIO, 1C F0 1C: The GMS/359 Speaks!

SERIO
1C F0 1C

Six bytes on a terminal. That's all it took to make me jump out of my chair.

The Moment

After yesterday's philosophical journey connecting lookup tables to ENIAC's glowing bulbs, today was about the real thing. The GMS/359, assembled and synthesized, waiting on the bench. A 2001 Mitsumi PS/2 keyboard plugged in. A terminal window open at 115200 baud.

Upload the IPL binary. Release reset.

SERIO

The UART greeting appeared. The system was alive. The CPU had executed LFI, ST, SIO, TIO, BC — the whole boot sequence. Video showed "IPL SYSINIT 1". Everything was ready.

And then: waiting. The cursor blinking. The keyboard waiting. Channel I/O suspended, polling for a keypress that hadn't happened yet.

I pressed 'A'.

1C F0 1C

Three hex pairs. Press and release. The scan code 1C, followed by the release sequence F0 1C.

It worked. It actually worked.

What Just Happened

Let me trace the path of that single keypress through the system:

       Finger
         ↓
┌─────────────────┐
│ Mitsumi PS/2    │ ← Mechanical switch closes
│ Keyboard (2001) │ → Sends serial bits at ~10kHz
└────────┬────────┘
         ↓ PS/2 clock + data
┌─────────────────┐
│ GMS 2591        │ ← Physical layer: ps2_rx deserializes
│ Keyboard Ctrl   │ → Byte 0x1C lands in FIFO
└────────┬────────┘
         ↓ WishBone bus
┌─────────────────┐
│ GMS 2870        │ ← Channel executes READ CCW
│ Multiplexor Ch  │ → Transfers byte to kbd_buffer
└────────┬────────┘
         ↓ Memory
┌─────────────────┐
│ GMS 2050 CPU    │ ← LB R1, [R8] loads scan code
│                 │ → AR R10, R1 computes table index
│                 │ → LB R2, [R10] fetches ASCII '1'
│                 │ → (repeat for 'C')
└────────┬────────┘
         ↓ Channel I/O
┌─────────────────┐
│ GMS 2870        │ ← Channel executes WRITE CCW
│ Multiplexor Ch  │ → Sends "1C " to UART
└────────┬────────┘
         ↓ Serial
┌─────────────────┐
│ UART Terminal   │ ← 115200 baud
│                 │ → "1C " appears on screen
└─────────────────┘

One keypress. Nine major components. Dozens of state machine transitions. Hundreds of clock cycles. And out comes 1C.

Then the key releases, the keyboard sends F0 1C, and the whole dance happens twice more: F0 , then 1C .

The Legendary Eight-Byte Monster

After the initial euphoria, I had to try it. The Pause key. The only key in the PS/2 protocol that uses the E1 prefix. The only key that sends its own release codes immediately. The eight-byte monster.

I pressed Pause.

E1 14 77 E1 F0 14 F0 77

Eight bytes. Eight complete cycles through the entire I/O stack:

E1 - Extended prefix (special!)
14 - First scan code
77 - Second scan code  
E1 - Extended prefix again
F0 - Release prefix
14 - First code release
F0 - Release prefix
77 - Second code release

The GMS/359 processed all eight without blinking. Eight SIO commands. Eight TIO polling loops. Eight table lookups. Eight UART transmissions. All in the time it takes to press and release a single key.


 

The Channel I/O architecture — inherited from the IBM System/360 — showed its elegance here. The CPU doesn't bit-bang the keyboard. It doesn't busy-wait on the UART. It issues high-level commands: "read a byte from device 11h", "write three bytes to device 12h". The Channel handles the details. The CPU moves on.

The Lookup Tables in Action

Remember yesterday's blog post about the hex conversion tables? The ones that reminded me of ENIAC's glowing decimal displays? They're not just theory anymore.

Every hex digit you see on that terminal came from a 256-byte table lookup:

LFI   R10, hex_hi_table     ; Table base
AR    R10, R1               ; Add scan code as offset
LB    R2, [R10]             ; Fetch ASCII digit

When the scan code is 1C:

  • hex_hi_table[0x1C] = '1' (because 0x1C is in the 0x10-0x1F range)
  • hex_lo_table[0x1C] = 'C' (because 0x1C & 0x0F = 0x0C)

The AR instruction — added just yesterday! — made this possible. Load base, add offset, fetch result. No shifts, no masks, no conditionals. Pure table-driven conversion.

The ENIAC engineers would approve.

What We Have Now

The GMS/359 Computing System, as of today:

CPU (GMS 2050):

  • 16 × 32-bit general purpose registers
  • 12 instructions: LR, AR, LFI, LB, SB, ST, BC, SIO, TIO, LPSW, MVI, NOPR
  • 24-bit addressing (16 MB address space)
  • Base register addressing with NASM-style syntax

Channel I/O (GMS 2870 Multiplexor):

  • S/360-style Channel Command Words
  • READ and WRITE operations
  • Device polling via TIO

Peripherals:

  • Video controller (device 10h) — text output to VGA
  • UART (device 12h) — serial I/O at 115200 baud
  • PS/2 Keyboard (device 11h) — with FIFO buffer

Memory:

  • 64 KB SRAM (directly addressable)

It's not much by modern standards. But it's real. It runs on actual hardware — a Cologne Chip GateMate FPGA. It executes real machine code. And now, it talks to a keyboard from 2001 and sends hex dumps to a terminal.

What's Next

The immediate TODO list:

  • 24-bit addressing cleanup
  • Console area (like a real S/360 operator console!) as another device connected to multiplexor channel
  • More instructions: SR (subtract), NR/OR/XR (logical), shifts
  • PROBLEM STATE 🤓 

The bigger picture:

  • Selector Channel (GMS 2860) for disk-like devices
  • Interrupt handling
  • Maybe, someday, enough to run a simple monitor program
  • If so, OS/359 

The Feeling

There's something deeply satisfying about watching bytes flow through a system you built from scratch. Every gate, every state machine, every instruction — you know where it came from. When 1C appears on the screen, you can trace it back through the UART, through the Channel, through the CPU, through the lookup table, through the AR instruction, all the way back to a finger pressing a key.

This is why we build things. Not because the world needs another PS/2 keyboard hex dumper. But because the act of building teaches us what we could never learn by reading. The IBM System/360 Principles of Operation manual is 150 pages of dense technical prose. But nothing in those pages prepared me for the feeling of seeing E1 14 77 E1 F0 14 F0 77 scroll across my terminal.


The GMS/359 Computing System: Because sometimes you need to build a 1960s mainframe to understand why the 1960s mattered.

Saturday, January 17, 2026

From ENIAC's Glowing Bulbs to FPGA Lookup Tables: 80 Years of the Same Idea

Sometimes, when you're deep in the weeds of assembly code at 2 AM, you write something and suddenly realize: "Wait, I've seen this before." Not in another codebase, not in a textbook — but in a grainy 1946 film about the birth of electronic computing.

Let me tell you about the unexpected connection between the GMS/359 project and ENIAC, and how a simple hex conversion routine turned into a journey through 80 years of computing history.

The Problem: Displaying Scan Codes in Hex

The GMS/359 system recently gained PS/2 keyboard support. The physical layer is working — bytes flow from an ancient Mitsumi keyboard through the GMS 2870 Multiplexor Channel into memory. But raw scan codes are just numbers. When debugging, you want to see 1C on your terminal, not an unprintable byte.

The challenge: convert a byte (0x00–0xFF) to two ASCII hex digits. On a modern CPU, this is trivial — shift, mask, add, conditionally adjust. But GMS/359 is intentionally minimal. We had Load Register, Store, Branch, and I/O instructions. No shifts. No bitwise AND. No comparison operations.

What we did have, as of this week, was the new AR (Add Register) instruction:

AR  R1, R2      ; R1 ← R1 + R2

And that's all we needed.

The Solution: 256-Byte Lookup Tables

Without bit manipulation, we use brute force with elegance — lookup tables. Two of them, each 256 bytes:

hex_hi_table: Given byte value N, returns the ASCII character for the high nibble (N >> 4) hex_lo_table: Given byte value N, returns the ASCII character for the low nibble (N & 0x0F)

The low nibble table looks like this:

hex_lo_table:
    DB '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'
    DB '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'
    DB '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'
    ; ... repeats 16 times total


The same pattern, repeated 16 times. Index 0x00 gives '0'. Index 0x0F gives 'F'. Index 0x10 gives '0' again. Index 0x1F gives 'F' again. The table encodes the low nibble extraction implicitly through its structure.

The conversion code is beautifully simple:

    LFI   R10, hex_hi_table     ; Load table base address
    AR    R10, R1               ; Add scan code as offset
    LB    R2, [R10]             ; Fetch the ASCII digit!

Three instructions. No shifts, no masks, no branches. Just load, add, load.

And Then I Saw It

When I looked at the repeating pattern of that table — '0' through 'F', sixteen times over — something clicked. I'd seen this visual pattern before. Not in code. In vacuum tubes.

This is ENIAC's decimal accumulator display from February 1946. Each row represents one decimal digit of a register. Each row has ten positions — ten bulbs. The lit bulb indicates the value. Position 0 lit means zero. Position 5 lit means five.

It's the same principle.

In ENIAC, the electron flow through vacuum tubes determined which bulb would glow. The physical position of the light is the value.

In GMS/359, the address offset into the table determines which byte we fetch. The position in the table is the value.

ENIAC (1946):
  ○○○○●○○○○○  = digit 4 (bulb at position 4 glows)

GMS/359 (2026):  
  '0','1','2','3','4','5'...
                   ↑
             index 4 = '4'

The bulbs are one-hot encoded decimal. The table is direct-mapped hexadecimal. Same concept, 80 years apart.

Looking at this image of an ENIAC operator at the console, surrounded by those glowing decimal displays, I realized something profound: we're still doing the same thing. The technology changed — vacuum tubes to transistors to FPGAs — but the fundamental ideas persist. Position encodes value. Structure encodes logic.

The New Instructions That Made This Possible

This hex display routine showcases several recent additions to the GMS 2050 CPU:

LFI (Load Fullword Immediate) — Load a 32-bit constant into a register. Essential for setting up pointers to data structures and tables.

LFI  R10, hex_lo_table    ; R10 = address of table (32-bit immediate)

AR (Add Register) — Add two registers. Simple, essential, and it sets condition codes. This is what enables computed addressing — table base plus offset.

AR   R10, R1              ; R10 = R10 + R1 (table + index)

LB (Load Byte) — Load a single byte from memory into a register, zero-extended. Now with full base register support!

LB   R2, [R10]            ; R2 = memory[R10]

SB (Store Byte) — Store the low byte of a register to memory. Used to build the output string.

SB   [R9+1], R2           ; Store low hex digit to buffer

The base register addressing deserves special mention. We moved from the original S/360 notation (LB R2, 0(R10)) to a cleaner NASM-style syntax: LB R2, [R10] or LB R2, [R10+5]. Square brackets for memory references, just like x86 assembly. It's a small thing, but it makes the code so much more readable.

The Full Picture

Here's the complete hex conversion in context:

kbd_loop:
    ; Read scan code from keyboard
    LFI   R1, kbd_ccw
    ST    [CAW], R1
    SIO   011h              ; Start keyboard read

poll_kbd:
    TIO   011h
    BC    4, poll_kbd       ; Wait for keypress...

    ; Load the scan code
    LFI   R8, kbd_buffer
    LB    R1, [R8]          ; R1 = scan code (0x00-0xFF)

    ; Convert high nibble
    LFI   R10, hex_hi_table
    AR    R10, R1
    LB    R2, [R10]         ; R2 = high hex digit ASCII
    LFI   R9, uart_buf
    SB    [R9], R2

    ; Convert low nibble  
    LFI   R10, hex_lo_table
    AR    R10, R1
    LB    R2, [R10]         ; R2 = low hex digit ASCII
    SB    [R9+1], R2

    ; Send to UART...

When you press a key, you see its scan code in hex on the terminal. Press 'A', see 1C. Release it, see F0 1C. Arrow keys show their E0 prefix bytes. It's immensely satisfying.

80 Years

ENIAC (1946)  →  IBM S/360 (1964)  →  GMS/359 (2026)
   tubes            transistors          FPGA (GateMate)
   decimal          EBCDIC/hex           ASCII/hex
   wired program    microcode            VHDL state machine
   18,000 tubes     hybrid ICs           ~5,000 LUTs
   30 tons          varies               41mm × 41mm

The ENIAC engineers would recognize what we're doing. The principle is the same. Position encodes information. Structure replaces logic. Tables trade space for time and complexity.

When I look at that repeating pattern — '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' — I don't just see bytes anymore. I see glowing bulbs in a Philadelphia basement in 1946. I see the fundamental ideas of computing, persistent and immortal, expressed in whatever medium we have at hand.

 The GMS/359 project started with Figure 1 of the IBM System/360 Principles of Operation — a block diagram that stuck in my mind for 15 years. Now it's grown to encompass ENIAC's ghosts too. We're not just building a retro computer. We're participating in a conversation that's been going on since the first electronic bit was stored.


The GMS/359 Computing System is an FPGA-based recreation of IBM System/360 architecture using the Cologne Chip GateMate platform. Current status: CPU with 12 instructions, working Channel I/O, video output, UART, and PS/2 keyboard support. The journey continues.

Friday, January 16, 2026

asm359 enhancements

There's something deeply satisfying about building tools for a processor that doesn't quite exist yet. The GMS/359 project — a custom FPGA-based computer inspired by IBM's legendary System/360 — has been an exercise in both nostalgia and pragmatism. Today I want to share some thoughts on the assembler toolchain we've built for it.

Why System/360?

When IBM introduced the System/360 in 1964, they created something revolutionary: a family of compatible computers spanning a wide range of performance levels, all running the same software. The architecture introduced concepts we take for granted today — byte-addressable memory, a clean separation between I/O and computation, and a unified Program Status Word that elegantly captures machine state.

But the S/360 also carries sixty years of accumulated baggage. Big-endian byte order made sense when humans read hex dumps on teletypes. The BALR/USING dance for establishing base registers was clever but tedious. Putting the opcode at the end of variable-length instructions optimized for hardware that no longer exists.

GMS/359 keeps what's beautiful about S/360 — the channel I/O model, the clean instruction formats, the PSW concept — while quietly modernizing the rest. Little-endian bytes. Opcode-first encoding. PC-relative addressing. No more base register juggling.

The "/359" isn't a typo. It's a declaration: inspired by, not compatible with.

Design Decisions That Matter

Destination First, Always

One principle drove many syntax decisions: destination first, always. This matches the convention in x86, ARM, and RISC-V. But we applied it consistently to both loads and stores:

    L     R1, [addr]      ; R1 ← memory  (destination = register)
    ST    [addr], R2      ; memory ← R2  (destination = memory)

This differs from ARM and RISC-V, where stores put the register first. Our reasoning: the destination is what you're changing. For a load, you're changing the register. For a store, you're changing memory. Both follow the same mental model.

Three Flavors of Load Immediate

Loading constants into registers is perhaps the most common operation in assembly programming. S/360's approach — load from a literal pool, or use LA to load 12-bit addresses — feels clunky today.

We added three new instructions:

  • LI (Load Immediate) — 20-bit zero-extended constant
  • LIS (Load Immediate Signed) — 20-bit sign-extended constant
  • LFI (Load Full Immediate) — full 32-bit constant

The 20-bit instructions fit in 4 bytes and cover most practical cases. When you need the full 32-bit range, LFI is there at 6 bytes. More importantly, LFI supports relocations — you can write LFI R1, my_data_label and the linker fills in the actual address.

The Preprocessor: Standing on NASM's Shoulders

We could have implemented a minimal preprocessor. Instead, we borrowed NASM's design — one of the most capable macro systems in any assembler. The implementation spans about 4,500 lines across ten modules, handling:

  • Single and multi-line macros with parameters
  • Full conditional assembly (%if, %ifdef, %ifidn, etc.)
  • String operations (%strlen, %substr, %strcat)
  • Context stacks for local symbol scopes
  • Repeat blocks with early exit

Why this complexity? Because real-world assembly programming needs it. Generating lookup tables, unrolling loops, creating type-safe data structures — all become possible with a proper macro system.

The Toolchain Takes Shape

An assembler that only outputs raw bytes is useful for experiments but not for real programs. Modern development needs:

  • Separate compilation — assemble modules independently, link later
  • Symbol resolution — let the linker find external references
  • Relocations — generate position-independent code

We based our object format on RDOFF2 (Relocatable Dynamic Object File Format), originally designed for NASM. It's simple enough to understand in an afternoon but complete enough for real work.

The link359 linker can produce two outputs:

  1. Relocatable modules — for further linking or dynamic loading
  2. Flat binaries — for burning into ROM or loading at a fixed address

That second option, triggered with -O bin -A 0x200, is essential for IPL (Initial Program Load) code. The GMS/359 hardware loads code at address 0x200 and starts executing — no bootloader, no filesystem, just raw machine code.

Bugs Are Features in Disguise

The most interesting bugs reveal hidden assumptions. While implementing structure definitions (struc/endstruc), we discovered that our string tokenizer didn't handle EOF correctly after quoted strings. The fix was one line, but finding it required understanding the entire token flow.

Another bug appeared when assembling from a subdirectory: %include "header.g5h" would fail because we only searched the current directory and explicit include paths. The fix was to also search relative to the source file's directory — obvious in hindsight, invisible until you hit it.

What's Next

The assembler is now capable enough to write real programs — IPL code, device drivers, even a simple monitor. The next frontier is the hardware itself: getting the VHDL design to pass timing on the GateMate FPGA, further implementing the channel I/O subsystem, testing I/O devices (such as keyboard and PSRAM), and many other exciting things.

There's something profound about building both the hardware and the software tools. When you write LFI R1, 12345678h and watch it become c0 10 78 56 34 12 in the binary, you understand every bit. When that binary eventually runs on your custom processor, executing instructions you designed on hardware you built — that's when computing feels like craftsmanship again.

The tools are open source. The hardware designs will follow. If you're interested in retrocomputing, FPGA development, or just building things from first principles, follow along at the blog.

73 de QRV Systems


Thursday, January 15, 2026

asm359: From Preprocessor to Working Assembler

Yesterday I had a working macro preprocessor. Today I have a working assembler that generates real GMS/359 machine code.

The Debug Hunt

The day started with a classic "it compiles but doesn't work" situation. The preprocessor built fine but %define was being interpreted as %elif. Classic symptom: wrong value being returned somewhere.

The culprit? A mismatch between the pptok.h enum values and the hash lookup function. NASM's original design used a perfect hash (computed by Perl scripts) where the array index equals the enum value. My simplified version returned array indices, but the enum values were carefully designed for conditional processing:

/* The enum assigns specific values for conditional grouping */
PP_ELIF = 0,      /* Base for %elif variants */
PP_IF = 32,       /* Base for %if variants */
PP_DEFINE = 67,   /* Way up here! */

When pp_token_hash("%define") returned 0 (the array index), the switch statement thought it was PP_ELIF. Chaos ensued.

The Fix

Rather than regenerating perfect hashes (which would require porting NASM's Perl infrastructure), I created a proper lookup table:

static const struct {
    const char *name;
    enum preproc_token token;
} pp_directive_table[] = {
    { "%elif",   PP_ELIF },
    { "%define", PP_DEFINE },
    /* ... all 80+ directives ... */
    { NULL, PP_INVALID }
};

Linear search? Yes. Fast enough for ~80 directives? Absolutely. The preprocessor is I/O bound anyway.

With that fixed, plus a couple of null pointer issues in macro expansion, the preprocessor started working. %define, %ifdef, %macro — all correct.

Building the Instruction Encoder

With preprocessing working, the next step was instruction encoding. GMS/359 uses the classic S/360 instruction formats, but with little-endian byte order:

RR-format:  [opcode] [R1<<4|R2]                    (2 bytes)
RX-format:  [opcode] [R1<<4|X2] [D2-lo] [B2<<4|D2-hi]  (4 bytes)
SI-format:  [opcode] [I2] [D1-lo] [B1<<4|D1-hi]    (4 bytes)
S-format:   [opcode] [00] [D2-lo] [B2<<4|D2-hi]    (4 bytes)

I implemented a complete instruction table with all the core S/360 instructions plus the GMS/359 I/O instructions:

Format Instructions
RR LR, AR, SR, MR, DR, CR, NR, OR, XR, LTR, LCR, LPR, LNR, BCR, BALR, BCTR
RX L, ST, A, S, M, D, C, N, O, X, LA, LH, STH, AH, SH, MH, CH, BC, BAL, BCT
SI MVI, NI, OI, XI, CLI, TM
S LPSW, SIO, TIO, HIO, TCH, HALT

Plus all the branch aliases: B, BH, BL, BE, BNE, BO, BP, BM, BZ, BR, NOP, NOPR...

The Test: IPL Bootstrap

To verify everything works, I assembled a real IPL bootstrap — the code that would run when the GMS/359 powers on:

; GMS/359 IPL: Display "HELLO" on console
%define CONSOLE 10h
%define CAW     48h

        ORG     200h
START:
        LA      R2,[CCW]
        ST      [CAW],R2
POLL:   TIO     CONSOLE
        BC      1,POLL
        SIO     CONSOLE
        BC      2,ERROR
WAIT:   TIO     CONSOLE
        BC      1,WAIT
        LPSW    [DONEPSW]

The assembler generated:

MVI [048h], 00h  →  92 00 48 00  ✓
SIO 010h         →  9c 00 10 00  ✓
TIO 010h         →  9d 00 10 00  ✓
BC  2, poll_loop →  47 20 10 00  ✓

Every byte correct. The instruction encoding matches my Python reference implementation exactly.

RDOFF Output

The final piece: generating linkable object files. I chose RDOFF (Relocatable Dynamic Object File Format) — NASM's simple, well-documented format. It's perfect for a homebrew system:

  • Clean header with module name and architecture tag
  • Separate code and data segments
  • Simple relocation records
  • No unnecessary complexity

The assembler now outputs .rdf files that can be linked with ldrdf or converted to raw binary with rdf2bin.

What's Working

  • ✅ Complete NASM-style macro preprocessor
  • ✅ All %define, %macro, %if, %rep directives
  • ✅ GMS/359 instruction encoding (RR, RX, RS, SI, S formats)
  • ✅ Labels and forward references
  • ✅ RDOFF v2 object file output
  • ✅ Two-pass assembly with proper error reporting

What's Next

The immediate next step is linking multiple object files and generating the final binary for the FPGA. Then: channel programs, device drivers, and eventually a small operating system.

Reflection

This project started as "let's build a simple assembler" and turned into a deep dive through 60 years of computer architecture. The S/360 designers knew what they were doing — channel I/O, the PSW, the clean instruction formats — it all still makes sense today.

And now I have the tools to write software for my own implementation of those ideas.

Wednesday, January 14, 2026

asm359: A Macro Preprocessor is Born

Today marks a significant milestone for the GMS/359 project: we have started the asm359 project, a standalone assembler with a fully functional macro preprocessor.

What is asm359?

It's an assembler for GMS/359 — my FPGA-based computing system inspired by IBM System/360. The key word is inspired: GMS/359 deliberately breaks compatibility with the original architecture to create something cleaner and more modern.

The Preprocessor

The heart of asm359 is its NASM-style macro preprocessor. Over ~4500 lines of carefully modularized C code, it supports:

  • Single-line macros: %define, %xdefine, %idefine
  • Multi-line macros: %macro/%endmacro with parameters
  • Conditional assembly: %if, %ifdef, %ifndef, %elif, %else
  • Repeat blocks: %rep/%endrep
  • String operations: %strlen, %strcat, %substr
  • Context stack: %push, %pop with local macros
  • File inclusion: %include with search paths

The code is organized into clean, focused modules:

pp_token.c      — Tokenizer
pp_macro.c      — Macro storage and lookup  
pp_expand.c     — Macro expansion engine
pp_directive.c  — Directive dispatcher
pp_context.c    — Context and state management
pp_main.c       — Public interface

Design Philosophy: Not Your Father's S/360

While implementing the assembler syntax, I crystallized the GMS/359 design principles:

IBM System/360 GMS/359
Big-endian Little-endian
Opcode last in instruction Opcode first
Source, Destination in ST Destination first (always)
Base+Displacement addressing PC-relative / Direct
BALR R12,0 + USING *,R12 Not needed!

That last point deserves explanation. In S/360, there was no way to read the program counter directly. Programs had to use BALR Rx,R0 to capture the current address into a register, then tell the assembler about it with USING. This was necessary because all memory references were encoded as base register + 12-bit displacement.

GMS/359 doesn't have this limitation. With PC-relative or direct 24-bit addressing, the assembler handles address resolution automatically. No more base register juggling!

Assembly Syntax

The syntax follows modern conventions:

; Destination first, like x86/ARM/RISC-V
LR    R5,R6            ; R5 ← R6
ST    [BUFFER],R3      ; memory ← R3

; Square brackets for memory references
LA    R2,[MSG]         ; Load address of MSG

; Hex suffix, no leading zero required
%define CONSOLE 10h

; NASM-style data directives  
DB    01h              ; Define Byte
DW    1234h            ; Define Word (2 bytes)
DD    value            ; Define Doubleword (4 bytes)
DQ    value            ; Define Quadword (8 bytes)

What's Next?

The preprocessor is complete. Next steps:

  1. Instruction encoding — RR, RX, RS, SI, SS formats
  2. Symbol table — Labels and forward references
  3. Output generation — Raw binary for now, maybe RDOFF later
  4. Integration — Feed directly to GMS/359 via IPL

The Bigger Picture

asm359 is part of a larger vision: a complete, self-consistent computing system that takes the best ideas from 1960s mainframe architecture and implements them with modern sensibilities. Channel I/O? Yes. Big-endian byte swapping headaches? No thank you.

The GMS 2050 CPU, GMS 2870 Multiplexor Channel, and GMS 2291 Video Controller are already running on my Cologne Chip GateMate FPGA. Soon they'll be executing code written in asm359.

Tuesday, January 13, 2026

Channel I/O Lives

"НЕЛЛО GMS/359!" — Three Days to a Working Mainframe

Day four. Multi-device channel I/O is operational.


 

The video controller (device 10h) and UART (device 12h) both work through the Multiplexor Channel. The CPU sets up a Channel Address Word pointing to Channel Command Words in memory, issues SIO, and the channel does the rest — fetching data, transferring it to the device, decrementing counts, all autonomously.

        ; Set up CAW pointing to CCW at 0300h
        MVI  [48h], 00h
        MVI  [49h], 03h
        MVI  [4Ah], 00h
        SIO  10h              ; Start I/O to video
POLL:   TIO  10h              ; Test I/O
        BB   POLL             ; Branch if Busy
        ...


That TIO/BC polling loop — that's how real mainframe programmers waited for I/O completion before interrupts were set up. Now I can do it too.

The BC instruction

Branch on Condition (opcode 47h) uses a 4-bit mask compared against the 2-bit condition code. The mapping is `mask = 8 >> CC` in IBM's bit numbering. For "Branch if Busy" (CC=2), the mask is 4. We added the extended mnemonic "BB" for readability.

The dual-area display

Rows 0-24: main display area (gray on black).  
Rows 25-29: console area (sky-blue on purple background).

Just like real mainframe terminals had a status line separated from the working area. The console area will eventually show system messages, CPU status, channel activity.

A small bug, a fun story

After "IPL OK", the first channel message appeared as "HЕЛЛО GMS/359" (yes, because of KOI-7 and because it was coded as "Hello") — but the IPL message was garbled to "IPL O". The 'K' vanished! A clock domain crossing glitch during the IPL-to-channel handoff. The K was there, then *poof*. Classic FPGA debugging moment.

Milestones in 4 days

| Day |           What Worked                  |
|-----|----------------------------------------|
|  0  | Concept, design decisions              |
|  1  | Video controller, IPL loader           |
|  2  | CPU executing instructions             |
|  3  | Full channel I/O with multiple devices |


From nothing to mainframe-style TIO polling loops in 72 hours. Not bad for an FPGA project built during evening tea sessions.

Tomorrow: PS/2 keyboard integration. Then this machine becomes interactive.

Monday, January 12, 2026

The Brain Awakes

 First CPU Instruction Executed on Real Hardware

Today the GMS 2050 CPU executed its first instruction. Actually, its first four instructions:

 ORG  200h
        SIO  10h              ; Start I/O
        TIO  10h              ; Test I/O
        LPSW [WAITPSW]        ; Load wait PSW → halt

WAITPSW:
        DD   000200h          ; Instruction Address
        DB   00h              ; Condition Code
        DB   01h              ; Wait bit = 1
        ...


The LED turned off. That might sound trivial, but it meant: the CPU decoded SIO, the CPU decoded TIO, the CPU decoded LPSW, and then stopped — because the Wait bit was set in the PSW. The LED was wired to the inverse of Wait. LED off = CPU waiting = everything worked.

The Program Status Word

This is where S/360's elegance really shines. Context switching? It's one instruction: LPSW. Load a 64-bit word from memory, and instantly you have a new instruction address, new condition code, new interrupt mask, new everything. RISC-V needs 50+ instructions to save and restore context. S/360 does it atomically in one.

I finally understand why IBM's engineers made certain choices. When you implement it in hardware yourself, the "why" becomes obvious.

What's actually implemented

- SIO (9Ch): Start I/O — pokes the channel, doesn't do much yet
- TIO (9Dh): Test I/O — returns "channel available" 
- LPSW (82h): Load PSW — the magic context switch
- NOPR (07h): No operation — for padding

The Multiplexor Channel is still a stub. But the CPU runs. It fetches, decodes, and executes. That's the milestone.

Sunday, January 11, 2026

KOI-7 Glows on the Screen

Day two, and the GMS 2291 Video Controller is alive!

640×480 VGA, 80×30 text mode, with a character set straight from 1987 — specifically, from А. Долгий's discrete TTL video controller design (with modifications, of course). Real ES EVM terminals (which I briefly saw at my alma mater back in 1996 -- for example, ТС-7063 manufactured in Kaniv, Ukraine) of course were using different fonts and character encodings (some variant of EBCDIC, I guess) -- but conceptually, "by spirit", it's similar. There's something satisfying about resurrecting that "7-bit spirit".

The clever clock trick

The GateMate's PLL can generate 25,113,600 Hz for video timing. Divide by 2, you get the system clock. Divide that by 109, and you get exactly 115,200 — the UART baud rate. One crystal, perfect integer divisions, zero jitter. Sheep №28 (my unofficial mascot) would be proud of this elegant ratio.

The IPL controller

Hardware-only bootstrap — no CPU involved. On power-up, it sends "IPL" via UART, waits for 16KB of data, stores it in memory, and displays "IPL OK" when done. Just like the real S/360 channel hardware loaded the first record without CPU intervention.

Debugging serial communication at midnight, discovering that USB-CDC adapters need flow control delays, eventually seeing those six beautiful letters appear:

IPL OK

A simple message, but it means: video works, UART works, memory works, the PLL is stable, all the clock domains are crossing correctly. Tomorrow we add a brain.

Saturday, January 10, 2026

"One Less Than 360"

GMS/359: Building a Mainframe on an FPGA

I've started a new project that's been brewing in my mind for years: building a System/360-inspired computer on a modern FPGA. Not a cycle-accurate emulator, not a software simulator — actual hardware, with real registers, real channels, and real blinking lights.

Why "359"? Because this is deliberately *less* than the mighty IBM System/360. It's a humble subset, a teaching machine, a love letter to the mainframe era written in VHDL.

The target platform: Cologne Chip GateMate A1-EVB — a small, affordable FPGA board with enough resources for a respectable retro computer.

Key design decisions made today:

1. Little-endian byte order. Yes, the real S/360 was big-endian. But for practical debugging with modern tools, little-endian makes hex dumps much more readable. We're *inspired by*, not *compatible with*, the original.

2. Opcode-first instruction format. When you look at a memory dump, `9C 00 10 00` immediately tells you "SIO to device 10h" — the opcode jumps out at you.

3. Channel-based I/O. This is non-negotiable. The whole point is to experience what mainframe I/O felt like — the CPU issues SIO, the channel takes over, DMA happens, interrupts fire. No memory-mapped I/O cheating!


 

The component naming follows IBM conventions: GMS 2050 (CPU), GMS 2291 (Video Controller), GMS 2870 (Multiplexor Channel), etc. Each will be a separate VHDL entity, connected via WishBone bus.

Tomorrow: the video controller. Let's put some characters on screen.