Micro Code

Michael's blog about teaching, hardware, software and the things inbetween

More on Oberon

July 08, 2018 — Michael Engel

Reading the Oberon source code, I got the impression that in a large number of places "magic numbers" are used without much explanation (neither in the code nor in the accompanying book).

One thing that had me wondering was the bitmap graphics generation. The Verilog source code (in VID.v) is quite simple. It uses a combination of simple counters to generate a 1024x768 pixel monochrome display. Based on a pixel clock counter, separate counters for the current horizontal (hcnt) and vertical (vcnt) coordinate are derived.

The video memory is addressed in 32-bit chunks, so each video line uses 1024/32 = 32 32-bit words (=128 bytes). The horizonal pixel counter is not 10 bits wide (counting 0..1023), but 11 bits, since it also counts the horizontal traceback time.

From the (10 bit) pixel position in hcnt, a 5 bit word offset in the current line is derived by cutting away the 5 least significant bits:

reg [10:0] hcnt;
reg [4:0] hword;  // from hcnt, but latched in the clk domain

always @(posedge clk) begin  // CPU (SRAM) clock domain
  hword <= hcnt[9:5];
end

If one wants to figure out which set of pixels is at what absolute memory address in SRAM, one stumbles across some strange things in the code:

localparam Org = 18'b1101_1111_1111_0000_00;  // DFF00: adr of vcnt=1023
assign vidadr = Org + {3'b0, ~vcnt, hword};

The binary constant in the Verilog code omits the two least significant bits of the address, since the 1 MB memory of the Oberon FPGA system is 32-bit word (4 byte) addressed; the address constant 0xDFF00 in hex gives the byte address of the frame buffer.

The display driver code in Display.Mod, however, gives a different frame buffer start address:

base = 0E7F00H;  (*adr of 1024 x 768 pixel, monocolor display frame*)

There's something fishy... the base address in the Oberon driver and the one in the Verilog source code differ by 0xE7F00-0xDFF00 = 0x8000 = 32768 bytes. So, what is going on here?

Pixels are set using the following function:

PROCEDURE Dot*(col, x, y, mode: INTEGER);
  VAR a: INTEGER; u, s: SET;
BEGIN a := base + (x DIV 32)*4 + y*128;
  s := {x MOD 32}; SYSTEM.GET(a, u);
  IF mode = paint THEN SYSTEM.PUT(a, u + s)
  ELSIF mode = invert THEN SYSTEM.PUT(a, u / s)
  ELSE (*mode = replace*)
    IF col # black THEN SYSTEM.PUT(a, u + s) ELSE SYSTEM.PUT(a, u - s) END
  END
END Dot;

The address for a pixel at position (x,y) is calculated as base + (x div 32) * 4 + y * 128. This sounds reasonable - each line is 128 bytes (128 * 8 = 1024 pixel) wide, x div 32 gives the word address offset in the current line, which is multiplied by 4 to give the word's byte address.

Accordingly, the pixel at address (0,0) is contained in the word at address 0xE7F00. But the Verilog source code starts reading the video buffer at address 0xDFF00... or does it? Let's take a closer look:

assign vidadr = Org + {3'b0, ~vcnt, hword};

When calculating the video memory address, the vcnt counter is inverted. Thus, the pixel at position (0,0) is at memory address (trailing "00" added to give the byte address):

0xDFF00 + { 000 1_1111_1111_1 000_00 00 } = 0xDFF00 + 0x1FF80 = 0xFFE80

Wait, that's still not right - what was the start address in the Oberon source again?

base = 0E7F00H;  (*adr of 1024 x 768 pixel, monocolor display frame*)

That's 0xFFE80-0xE7F00 = 0x17F80 = 98176 (dec.) bytes from the start!

Hmmm, wait... that's vaguely familiar. The whole monochrome 1024x768 bitmap screen uses

1024 / 8 * 768 = 98304 bytes

and 98304-98176 = 128, the memory used by one line of pixels. This works out since we were calculating the address of the leftmost pixel in the line.

Maybe you have already figured out what is going on here... Oberon's graphics coordinate system starts in the lower left corner of the screen, whereas the monitor's coordinate system starts at the upper left corner (see Chapter 4 in Wirth's Oberon book). Thus, the line at Oberon's coordinate y = 0 is the 767th line in monitor coordinates. Accordingly, inverting the y line coordinate in the Verilog address calculation

assign vidadr = Org + {3'b0, ~vcnt, hword};

causes the vertical line counter to count backwards. Note that the x coordinates of Oberon and the monitor count from left to right, so hword is not inverted.

The final remaining question is why the video base address is different. This is now also rather easy to see. vcnt is a 10-bit counter, so it counts from 0...1023. The Oberon y coordinates only are valid up to 767, so the 767th Oberon line = 0th monitor video line is at address

767 = 10_1111_1111 (binary), inverted = 01_0000_0000
0xDFF00 + { 000 0_1000_0000_0 000_00 00 } = 0xDFF00 + 0x08000 = 0xE7F00

Voila, there's the video memory start address that's defined in the Oberon driver source code.

If you take a closer look at the Draw function, you might now wonder what will happen if you pass a y coordinate larger than 767 to the Draw function. More on this in an upcoming blog post...

Tags: FPGA, Oberon, Xilinx, Wirth, graphics

Small is beautiful!

June 30, 2018 — Michael Engel

If you got into contact with computers in the 1980s (at least in Europe), there's a good chance that you have learned to program in Pascal, one of the languages designed by Prof. Niklaus Wirth at ETH Z├╝rich.

Even though he is retired for several years now, to this day Prof. Wirth continues to design software and even hardware systems that follow the maxime "small is beautiful" - elegant and simple systems including more recent programming languages such as Modula-2 and Oberon, the whole Oberon system environment including a simple OS and a graphical user interface, and even complete computer systems. In-house designs at ETH were the early Lilith and Ceres workstations in the 1980s; more recently, Prof. Wirth designed a small RISC system on an FPGA including all peripherals that is able to run Oberon.

You can find more information on the Oberon system including all software and Verilog source code as well as a textbook describing the complete design at Project Oberon. Reading the sources is a nice project for a rainy weekend - the whole Oberon system's source code, including the compiler and GUI, is only about 10k LoC (written in Oberon, of course). The Verilog sources including the CPU, all peripheral interfaces and the video driver, amount to less than 1000 lines - impressive!

The Oberon FPGA system originally ran on a quite old FPGA board, the Xilinx Spartan 3 starter board, which features a Spartan 3S400, a tiny (by today's standards) FPGA, 1 MB of fast 32-bit SRAM, and a number of peripheral interfaces. I recently found my old Spartan 3 board and finally got the system to run.

Oberon on FPGA

In order to use the Oberon system, you have to connect a PS/2 keyboard and VGA monitor to the onboard ports. In addition, you need to connect an SD card (containing the Oberon file system image) and a PS/2 mouse.

For reference (you need to look it up in the ucf pinout definition file and figure out that SPI0 is the one the SD card is supposed to be connected to - SPI1 is reserved for some wireless network interface), here are the necessary connections to connector A2 on the board:

  • SD MISO: A2 pin 17
  • SD MOSI: A2 pin 7
  • SD SCLK: A2 pin 13
  • SD SSEL: A2 pin 5
  • SD Vcc (3.3V): A2 pin 3
  • PS/2 CLK: A2 pin 4
  • PS/2 DAT: A2 pin 6
  • PS/2 Vcc (5V): A2 pin 2
  • GND: A2 pin 1

I have used a generic Logitech PS/2 mouse (type M-BJ69) and a Waveshare Micro-SD adapter board with a SanDisk 8 GB card.

Tags: FPGA, Oberon, Xilinx, Wirth

Open source FPGA development on a tiny budget

August 05, 2017 — Michael Engel

Last week, I have received the delivery of an Olimex iCE40HX1K-EVB FPGA development board. This board contains a small Lattice iCE40-series FPGA with 1280 LUTs as well as a 256k x 16 SRAM.

iCE40HX1K-EVB board

The exciting thing about this board (apart from its low price) is that the bitfile format of the iCE40 FPGAs has been reverse engineered, which made a complete open source toolchain possible that is able to synthesize Verilog code. The Verilog synthesis is handled by the Clifford Wolf's yosys suite, which is integrated into the IceStorm toolchain.

The synthesis does not need large amounts of computing power -- and since all components are open source, I was able to run all tools (including programming the FPGA via SPI) on a Raspberry Pi 3! This probably makes it one of the smallest FPGA development systems...

At the moment, the board is running some simple SRAM test code. I am trying to squeeze a tiny SoC onto the FPGA. James Bowman's J1 Forth CPU should fit, it will be interesting to see if a slightly bigger core, such as Niklaus Wirth's RISC0 core, will fit together with a few peripherals.

Raspberry Pi FPGA development system

Tags: FPGA, Lattice, RaspberryPi, yosys, icestorm, open source