InhaltsverzeichnisBeteiligte
IdeeIm 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. UmsetzungEs 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 Schaltplan: Was kann man mit dem Mini Z80 machenHeutige 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. Ein Programm in den Mini Z80 ladenHerr 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 |