Inhaltsverzeichnis

Beteiligte

Idee

Im Mai 2015 gab es einen Doppelvortrag im CCC-FFM Hackquarter von Herrn Professor Bernd Ulmann über Analogrechner und den Selbstbau eines Z80 basierten Computer:

https://ccc-ffm.de/2015/02/doppelvortrag-analogrechnen-z80-selbstbau-2/

Dadurch enstand die Ideee selbst eine Version dieses Mini Computers zu bauen.

Umsetzung

Es gibt 2 Varianten des Z80 Computers von Professor Ulmann:

Eine Version mit Cpu Board und Flashspeicher Board (Tiny Z80)

http://vaxman.de/projects/tiny_z80/

Eine vereinfachte Variante (Really Tiny Z80)

http://vaxman.de/projects/Z80_mini/index.html

Durch die etwas einfachere Aufbauweise des Really Tiny Z80 wurde der Entschluss gefasst diese Variante nachzubauen.

Die Bauzeit für dieses Projekt war von Mai 2016 bis August 2016 im CCC-FFM Lab.

Folgende Bauteile wurden verbaut:

CPU: Zilog Z80
Eprom: M27C256
UART: TL16C550C
Treiber: Max232
RAM: AS6C62256
6 Mhz Oszillator für CPU
1,8553 Mhz Oszillator für UART
74LS32 Oder Gatter (Für Reset Beschaltung)
74LS14 Schmitt Trigger (CE /OE Ansteuerung)
Diverse Kondensatoren, Diode, Widerstände für Reset und Spannungsversorgung

Schaltplan

Verändertes Board des Really Tiny Z80

Es wurde eine veränderte Version des von Professor Ulmann gefertigtem Board erstellt.

Folgende Änderungen wurden vorgenommen:

Hier die Eagle 7.6.0 Dateien:

z80_ff.zip

Und als Gerber Files / Cam Job Version

z80_ff_cam_job.zip

Was kann man mit dem Mini Z80 machen

Heutige Einplatinen / Ein-Chip Computer wie der Raspberry Pi, Adurino u.a. sind zu kompliziert um die Grundlagen eines Computers zu verdeutlichen.

Ausserdem sind die wichtigsten Komponenten wie UART oder RAM im SOC Chip verbaut oder nicht mehr so einfach zu erkennen.

Mit dem Mini Z80 Computer hat man die Möglichkeit diese Bauteile noch zu sehen sowie die Grundlagen eines Prozessors / das Auslesen des Arbeitspeichers / die Assembler Programmierung einfacher und nicht so hoch komplex wie in moderen CPUs nachzuvollziehen.

Herr Professor Ulmann hat ein einfaches Betriebssystem (Monitor) für den Z80 Mini Computer geschrieben der folgende Funktionen bietet (In Gruppen organisiert):

Control Group: Für Kalt und Warmstart sowie Info zum Monitor.
Disk Group: In der Mini Z80 Version nicht verwendet.
File Group: In der Mini Z80 Version nicht verwendet.
Help: Zeigt alle Funktionen an die das Monitorprogramm bietet.
Memory Group: Dissasembeln / Dump / Untersuchen (Examine) / Füllen (Fill) / Laden von Werten (Load) in den Speicher, das Uploaden eines Programms in den Speicher mit Intel Hex Load sowie ein Register Dump.
Subsystem Group: Ein Basic und Forth Interpreter.

Ein Programm in den Mini Z80 laden

Herr Professor Ulmann bietet auf seiner Homepage diverse Assembeler Beispiele zum Download an u.a eine Mandelbrotmenge / Apfelmännchen Programm.

Um dieses Apfelmännchen Programm in den Z80 zu laden braucht man folgendes:

Ein Assembler Programm für den Z80 Prozessor z.B. Zasm http://k1.spdns.de/Develop/Projects/zasm/Distributions/

Das Apfelmännchen Programm muss in Zasm mit dem Parameter -x in ein Intel-Hex-File kompiliert werden.

zasm -x mandel.asm

(Für das Apfelmänchenprogramm siehe unten)

Das funktioniert out-of-the-box leider nur fuer Files, deren .ORG-Adresse gleich $0000 ist, da solche Hex-Files immer bei $0000 anfangen muessen. Da mandel.asm und andere Beispielprograemmchen bei $8000 anfangen, muss man das entstandene hex-File noch etwas anpassen, wozu man ein paar Zeilen Perl-Code nehmen kann:

#!/opt/local/bin/perl

#
#  The zasm Z80 assembler has a bug when generating INTEL hex files for
burning
# EPROMS: The initial ORG-directive has no influence on the addresses
generated.
# Addresses always start at $0000. This small program reads such a buggy ROM-
# file, adds a delta to the addresses, computes a new checksum and prints the
# repaired record to stdout from where it can be copied to the EPROM
programmer
# or an appropriate loader in the monitor of the Z80 SBC.
#
# 30-OCT-2011, B. Ulmann
#

use strict;
use warnings;

die "Usage: $0 <address_offset> <intel_hex_file>\n" unless @ARGV == 2;

my $offset = $ARGV[0];
open my $handle, '<', $ARGV[1] or die "Unable to open file $ARGV[1]: $!\n";
while (my $line = <$handle>)
{
    chomp $line;
    my ($len, $address, $data) = $line =~ m/^:(..)(....)(.*)(...)$/;
    $address = sprintf('%04X', hex($address) + hex($offset));
    my $checksum = sprintf('%02X', 
        (256 - unpack("%8C*", pack('H*', "$len$address$data"))) & 0xff);
    print ":$len$address$data$checksum\n";
}
close $handle;

Das Programm (z.B. unter dem Namen rom_renumber.pl gespeichert) laesst sich dann wie folgt anwenden:

perl rom_renumber.pl 8000 mandel.rom.hex

Es schreibt dann einfach auf stdout das passende Intel-Hex-Format mit entsprechenden Adressen ab $8000:

:2080000021EA80DD210700CF2A4081ED5B3E81A7ED52FAE1802A38812236812A3A81ED5BE5
:208020003681A7ED52FACE802100002244812246813A358147C5ED5B4681424BCD5A812208
:208040004C81ED534A81ED5B4481424BCD5A81225081ED534E81A7ED4B4C81ED422248813E
:20806000626BED4B4A81ED42ED4B48814845C52A448129545DED4B4681CD5A81434C2A3E47
:208080008109224681C12A3681092244812A4C81ED5B508119444D2A4A81ED5B4E81ED5ACE
:2080A0006568010004A7ED423803C11803C1108578E607ED626F115281197EDD210600CF3A
:2080C000ED5B3C812A368119223681C31B80DD210400CFED5B42812A3E8119223E81C308E0
:2080E00080211D81DD210700CFC747656E65726174696E672061204D616E64656C62726F6D
:2081000074207365742C20422E20556C6D616E6E2C204A554E2D323031330D0A00436F6DD6
:208120007075746174696F6E2066696E69736865642E0D0A000A000000FE80000A0000FF8B
:20814000000119000000000000000000000000000000202E2D2B2A3D2340AFCB78280791E3
:208160004F3E00984737CB7A280AF5AF935F3E009A57F13F08A7ED623E1029CB13CB123090
:1A81800004093001133D20F208D0AF956F3E009C673E009B5F3E009A57C949
:008000017F

Das einfach in den Copy-Buffer uebernehmen, im Monitor M I tippen und dann mit CTRL-V einfuegen. Dann mit C S 8000 starten.

Das Apfelmännchenprogramm:

;
;  Compute a Mandelbrot set on my Z80 computer (for a description of this 
; machine see http://www.vaxman.de/projects/tiny_z80/). 
;
; June 2013, B. Ulmann
;
;  Porting this program to another Z80 platform should be easy and straight-
; forward: The only dependencies on my homebrew machine are the system-calls 
; used to print strings and characters. These calls are performed by loading
; IX with the number of the system-call and performing an RST 08. To port this
; program to another operating system just replace these system-calls with 
; the appropriate versions. Only three system-calls are used in the following:
; _crlf: Prints a CR/LF, _puts: Prints a 0-terminated string (the adress of 
; which is expected in HL), and _putc: Print a single character which is 
; expected in A. RST 0 give control back to the monitor.
;

#include        "../monitor/mondef.asm"

                org     ram_start

scale           equ     256                     ; Do NOT change this - the 
                                                ; arithmetic routines rely on
                                                ; this scaling factor! :-)
divergent       equ     scale * 4

                ld      hl, welcome             ; Print a welcome message
                ld      ix, _puts
                rst     08

; for (y = <initial_value> ; y <= y_end; y += y_step)
; {
outer_loop      ld      hl, (y_end)             ; Is y <= y_end?
                ld      de, (y)
                and     a                       ; Clear carry
                sbc     hl, de                  ; Perform the comparison
                jp      m, mandel_end           ; End of outer loop reached

;    for (x = x_start; x <= x_end; x += x_step)
;    {
                ld      hl, (x_start)           ; x = x_start
                ld      (x), hl
inner_loop      ld      hl, (x_end)             ; Is x <= x_end?
                ld      de, (x)
                and     a
                sbc     hl, de
                jp      m, inner_loop_end       ; End of inner loop reached

;      z_0 = z_1 = 0;
                ld      hl, 0
                ld      (z_0), hl
                ld      (z_1), hl

;      for (iteration = iteration_max; iteration; iteration--)
;      {
                ld      a, (iteration_max)
                ld      b, a
iteration_loop  push    bc                      ; iteration -> stack
;        z2 = (z_0 * z_0 - z_1 * z_1) / SCALE;
                ld      de, (z_1)               ; Compute DE HL = z_1 * z_1
                ld      bc, de
                call    mul_16
                ld      (z_0_square_low), hl    ; z_0 ** 2 is needed later again
                ld      (z_0_square_high), de

                ld      de, (z_0)               ; Compute DE HL = z_0 * z_0
                ld      bc, de
                call    mul_16
                ld      (z_1_square_low), hl    ; z_1 ** 2 will be also needed
                ld      (z_1_square_high), de

                and     a                       ; Compute subtraction
                ld      bc, (z_0_square_low)
                sbc     hl, bc
                ld      (scratch_0), hl         ; Save lower 16 bit of result
                ld      hl, de
                ld      bc, (z_0_square_high)
                sbc     hl, bc
                ld      bc, (scratch_0)         ; HL BC = z_0 ** 2 - z_1 ** 2

                ld      c, b                    ; Divide by scale = 256
                ld      b, l                    ; Discard the rest
                push    bc                      ; We need BC later

;        z3 = 2 * z0 * z1 / SCALE;
                ld      hl, (z_0)               ; Compute DE HL = 2 * z_0 * z_1
                add     hl, hl
                ld      de, hl
                ld      bc, (z_1)
                call    mul_16

                ld      b, e                    ; Divide by scale (= 256)
                ld      c, h                    ; BC contains now z_3

;        z1 = z3 + y;
                ld      hl, (y)
                add     hl, bc
                ld      (z_1), hl

;        z_0 = z_2 + x;
                pop     bc                      ; Here BC is needed again :-)
                ld      hl, (x)
                add     hl, bc
                ld      (z_0), hl

;        if (z0 * z0 / SCALE + z1 * z1 / SCALE > 4 * SCALE)
                ld      hl, (z_0_square_low)    ; Use the squares computed
                ld      de, (z_1_square_low)    ; above
                add     hl, de
                ld      bc, hl                  ; BC contains lower word of sum

                ld      hl, (z_0_square_high)
                ld      de, (z_1_square_high)
                adc     hl, de

                ld      h, l                    ; HL now contains (z_0 ** 2 + 
                ld      l, b                    ; z_1 ** 2) / scale

                ld      bc, divergent
                and     a
                sbc     hl, bc

;          break;
                jr      c, iteration_dec        ; No break
                pop     bc                      ; Get latest iteration counter
                jr      iteration_end           ; Exit loop

;        iteration++;
iteration_dec   pop     bc                      ; Get iteration counter
                djnz    iteration_loop          ; We might fall through!
;      }
iteration_end
;      printf("%c", display[iteration % 7]);
                ld      a, b
                and     $7                      ; lower three bits only (c = 0)
                sbc     hl, hl
                ld      l, a
                ld      de, display             ; Get start of character array
                add     hl, de                  ; address and load the 
                ld      a, (hl)                 ; character to be printed
                ld      ix, _putc               ; Print the character
                rst     08

                ld      de, (x_step)            ; x += x_step
                ld      hl, (x)
                add     hl, de
                ld      (x), hl

                jp      inner_loop
;    }
;    printf("\n");
inner_loop_end  ld      ix, _crlf               ; Print a CR/LF pair
                rst     08

                ld      de, (y_step)            ; y += y_step
                ld      hl, (y)
                add     hl, de
                ld      (y), hl                 ; Store new y-value

                jp      outer_loop
; }

mandel_end      ld      hl, finished            ; Print finished-message
                ld      ix, _puts
                rst     08

                rst     0                       ; Return to the monitor

welcome         defb    "Generating a Mandelbrot set, B. Ulmann, JUN-2013"
                defb    cr, lf, eos
finished        defb    "Computation finished.", cr, lf, eos

iteration_max   defb    10                      ; How many iterations
x               defw    0                       ; x-coordinate
x_start         defw    -2 * scale              ; Minimum x-coordinate
x_end           defw    5 *  scale / 10         ; Maximum x-coordinate
x_step          defw    4  * scale / 100        ; x-coordinate step-width
y               defw    -1 * scale              ; Minimum y-coordinate
y_end           defw    1  * scale              ; Maximum y-coordinate
y_step          defw    1  * scale / 10         ; y-coordinate step-width
z_0             defw    0
z_1             defw    0
scratch_0       defw    0
z_0_square_high defw    0
z_0_square_low  defw    0
z_1_square_high defw    0
z_1_square_low  defw    0
display         defb    " .-+*=#@"              ; 8 characters for the display

;
;   Compute DEHL = BC * DE (signed): This routine is not too clever but it 
; works. It is based on a standard 16-by-16 multiplication routine for unsigned
; integers. At the beginning the sign of the result is determined based on the
; signs of the operands which are negated if necessary. Then the unsigned
; multiplication takes place, followed by negating the result if necessary.
;
mul_16          xor     a                       ; Clear carry and A (-> +)
                bit     7, b                    ; Is BC negative?
                jr      z, bc_positive          ; No
                sub     c                       ; Complement BC
                ld      c, a
                ld      a, 0                    ; Do not destroy carry!
                sbc     a, b
                ld      b, a
                scf                             ; Set carry (-> -)
bc_positive     bit     7, D                    ; Is DE negative?
                jr      z, de_positive          ; No
                push    af                      ; Remember carry for later!
                xor     a                       ; Clear carry and A
                sub     e                       ; Complement DE
                ld      e, a
                ld      a, 0                    ; Do not destroy carry!
                sbc     a, d
                ld      d, a
                pop     af                      ; Restore carry for complement
                ccf                             ; Complement Carry (-> +/-?)
de_positive     ex af, af'                      ; Remember state of carry
                and     a                       ; Start multiplication
                sbc     hl, hl
                ld      a, 16                   ; 16 rounds
mul_16_loop     add     hl, hl
                rl      e
                rl      d
                jr      nc, mul_16_exit
                add     hl, bc
                jr      nc, mul_16_exit
                inc     de
mul_16_exit     dec     a
                jr      nz, mul_16_loop
                ex af, af'                      ; Restore carry from beginning
                ret     nc                      ; No sign inversion necessary
                xor     a                       ; Complement DE HL
                sub     l
                ld      l, a
                ld      a, 0
                sbc     a, h
                ld      h, a
                ld      a, 0
                sbc     a, e
                ld      e, a
                ld      a, 0
                sbc     a, d
                ld      d, a
                ret