====== Beteiligte ======
* Professor Bernd Ulmann (Ideengeber für den Z80 Mini Computer: http://vaxman.de/about_me/about.html)
* FF (Nachbau des Mini Z80 Computer http://vaxman.de/projects/Z80_mini/index.html)
* Hias (Hilft bei angedachter zweiten Version)
====== 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)
{{:playground:boards.jpg?200|}}
http://vaxman.de/projects/tiny_z80/
Eine vereinfachte Variante (Really Tiny Z80)
{{:playground:overall.jpg?200|}}
http://vaxman.de/projects/Z80_mini/index.html
Durch die etwas einfachere Aufbauweise des Really Tiny Z80 wurde der Entschluss gefasst diese Variante nachzubauen.
{{:playground:20161103_233136.jpg?200|}}
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 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:
{{:playground:schematic.jpg?500|}}
====== 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 Compiler.
====== 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 \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 = ; 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