Scott's TinyBasicPlus ported (and improved!) to RC2014

1,010 views
Skip to first unread message

Filippo Bergamasco

unread,
Jun 6, 2016, 5:11:57 PM6/6/16
to rc201...@googlegroups.com
Dear all,
after some work I've finally managed to successfully port the Scott's TinyBasicPlus (https://github.com/BleuLlama/TinyBasicPlus) to our loved RC2014 system.

These are roughly the things I made:
- I've coded a new RC2014 initialisation code (the one that initialises the ACIA chip, setup the stack etc)
- I've started to implement the PiGFX assembly library to easily interface the PiGFX graphic card
- I've ported and tested TinyBasicPlus to the Z80 via the z88dk development kit (this includes the development of some "glue" code and fixing some compiler-induced strange bugs)
- I've added a couple of commands to the original TinyBasicPlus:
ink <col> to change the text color
paper <col> to change the text background color.


I've only tested it on the simulator (still I haven't found an eprom programmer yet) and it looks well. Spencer, if you have time to test it on the real hardware it would be awesome. Attached to this mail you can find the compiled binary code (TinyBasicPlus.bin) and the same stuff in intel hex format (TinyBasicPlus.hex).

If you want to look at the source code, I've grouped all the RC2014-related things I'm doing here:

https://github.com/fbergama/pigfx/tree/RC2014/RC2014


to test it just type:

10 for i=1 to 20
20 ink i
30 paper i+1
40 print "RC2014 is awesome"
50 next i
end
run

Thank you all!
Filippo


TinyBasicPlus.hex
TinyBasicPlus.bin

Scott Lawrence

unread,
Jun 6, 2016, 5:14:12 PM6/6/16
to rc201...@googlegroups.com
Awesome!!

--
You received this message because you are subscribed to the Google Groups "RC2014-Z80" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rc2014-z80+...@googlegroups.com.
To post to this group, send email to rc201...@googlegroups.com.
To view this discussion on the web, visit https://groups.google.com/d/msgid/rc2014-z80/CAK%2B%3DWr9y76%3D7skujqveY8vQu_jhme1%3DiXtObZP6r6Q8ecZSXVA%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.



--
Scott Lawrence
yor...@gmail.com

Filippo Bergamasco

unread,
Jun 6, 2016, 5:21:32 PM6/6/16
to rc201...@googlegroups.com
Oh fuck, I've just realized that the binary rom is 17k :( it would never fit on an 8k prom.
We really need an SD bootloader -_-

- Filippo

Spencer Owen

unread,
Jun 6, 2016, 5:39:17 PM6/6/16
to rc201...@googlegroups.com
Ha ha! Maybe we just need to rename TinyBasic to SlightlyOverweightBasic ;-)

Seriously though, that is just awesome!  I am just thrilled about the love people are giving this little computer.

If you are able to trim it down to just a little bit so it's below 16k then that would be fine and would only take a little bit of reworking to get it working.  And that would only take 1/4 of a 64k chip, rather than 17k which effectively uses 1/2 of a chip.

Cheers

Spencer

Filippo Bergamasco

unread,
Jun 6, 2016, 6:13:16 PM6/6/16
to rc201...@googlegroups.com
Sorry! I was too excited to see that is finally working that I completely forgot to check the size :P 
Scott was right to say that the C compiler would end up bloating the code even for simple things.

Anyway... I think I can shrink it below 16k. I just need to sleep so that tomorrow I'll be fresh to think about it (well... On my free time at least)

Well.. You know I was a bit too young to properly live the exciting period of commodore, zx spectrum etc. So I always wanted to experiment a bit with that kind of stuff. It turned out that instead of rebuild something new is just more fun to be part of a good group of folks that share the same passion :)


- Filippo

Scott Lawrence

unread,
Jun 6, 2016, 7:43:17 PM6/6/16
to rc201...@googlegroups.com
Working on it... ;)

Sent from my fancy-schmancy phone.

Scott Lawrence

unread,
Jun 6, 2016, 7:44:22 PM6/6/16
to rc201...@googlegroups.com
I bet that we could pare down the output and hand optimize bits to make it smaller...  Or find the space somehow from it ...

S


Sent from my fancy-schmancy phone.

Filippo Bergamasco

unread,
Jun 7, 2016, 1:49:40 AM6/7/16
to rc201...@googlegroups.com
Hold on...
If all the code il located on a read only memory I cannot just read and write on the "program" array (Scott hope you remember your basic source code) as it would for sure be located in the wrong memory area. 
I've never had such issues in my life since I usually have all the _ram_ space at my service ;) so.. Without a linker script that places the data section at the correct place I need to work a little bit more to fix these problems

Probably I should start by modifying the emulator so that it warns for all the write accesses on the lower 16k or 32k of the address space. That would be very useful in the future

Sorry if this mail is not particularly interesting to you, I was only thinking "loudly" :)

- Filippo

Scott Lawrence

unread,
Jun 7, 2016, 2:39:08 AM6/7/16
to rc201...@googlegroups.com
I only expanded Tiny Basic a little, for use in AVRs... hence my addtion of "plus".  I didn't write the original BASIC code.  on that architecture, or if you were to just load it in to RAM like on a regular OS, then it would not be an issue.

To be completely honest, I totally forgot that it just uses arrays within the program space... there's no malloc or anything like that, or a way to set ram space elsewhere on AVR, so it wasn't an issue.  Everything was always in RAM when run... The compiler there shifted it all around for you there.

So... yeah. I guess it will require a bit of work to get that working for RC.  :/


-s 


For more options, visit https://groups.google.com/d/optout.



--
Scott Lawrence
yor...@gmail.com

Filippo Bergamasco

unread,
Jun 7, 2016, 2:46:45 AM6/7/16
to rc201...@googlegroups.com
Well that would add some fun, right? :)

I'm generally referring TinyBasicPlus as "your code" for simplicity. Don't worry, I've mentioned all the original authors both in the sources and at startup.

In de facto standard compilers like gcc you can provide a "linker script" that tells the linker where to put all the relevant stuff. For instance the program code can start at 0x0000, the read only variables at 0xFA00, the static variables elsewhere, and so on. Since AVRs use gcc they already provide the correct linker scripts for that architecture.
In my case.. I'll need to hard-code some stuff a bit but it can certainly be done (sacrificing some portability.. But who cares)

- Filippo

A A

unread,
Jun 8, 2016, 2:52:16 AM6/8/16
to RC2014-Z80


On Monday, June 6, 2016 at 11:49:40 PM UTC-6, Filippo Bergamasco wrote:
Hold on...
If all the code il located on a read only memory I cannot just read and write on the "program" array (Scott hope you remember your basic source code) as it would for sure be located in the wrong memory area. 
I've never had such issues in my life since I usually have all the _ram_ space at my service ;) so.. Without a linker script that places the data section at the correct place I need to work a little bit more to fix these problems



Hi there,

We had someone asking about an rc2014 at z88dk and since I'd never heard of one before, a google search led me here :)

You should be made aware that there are two c libraries and two c compilers in z88dk currently.

One C compiler is sccz80 which is derived from small C but z88dk's version has seen continuous development over the past 30 years so it's had most of the limitations of small C removed.  For example, floating point is supported, ANSI C declarations are supported, 8/16/32-bit integers are supported and so on.  It is a little short of C89 compliance with a few notable non-compliances being multi-dimensional arrays and function pointer prototyping.

The other C compiler is a patch of sdcc, another open source compiler that attempts to implement subsets of C89, C99 and C11.  sdcc is an optimizing compiler and z88dk's patch improves on sdcc's output by supplying some bugfixes not yet incorporated into sdcc itself and by supplying a very large set of peephole rules to further improve output.

You can choose which C compiler you use by selecting the appropriate switch on the command line.  In your makefile you are using sccz80.  To use sdcc, "-clib=sdcc_ix" or "-clib=sdcc_iy" would appear in the compile line.


Then there are two C libraries.

The classic C library is the C library that has always shipped with z88dk.  It has many crts available for it that allows compiling for a lot of target machines out of the box.  The level of library support varies by target with the best supported having sprite libraries, sound, graphics, etc supplementing the standard c library.  It is mostly written in machine code and has a small stdio implementation.  However, at this time it cannot be used to generate ROMable code as it mixes variables with code in the output binary.  It's also not compatible with sdcc at this time.  Both of these issues are being addressed now.

The new C library is a rewrite from scratch with the intention of meeting a subset of C11 compliance.  It is 100% machine code, is written to be compatible with any C compiler, and can generate ROMable code with separation of ROM and RAM data.  The stdio model is object oriented and allows device drivers to be written using code inheritance from the library.  Although it's not finished (it's missing disk io and non-blocking io), it is in an advanced state.

The choice of C library is made on the compile line.  "-clib=new", "-clib=sdcc_ix" and "-clib=sdcc_iy" all use the new C library.  Anything else uses the classic C library.  In order to generate ROMable code, you should really be using the new C library but your makefile is using the classic C library which will (currently) mix ram vars with rom code.


z88dk's C library is different from other compilers in that it is written in assembly language, so it is more compact and faster than other z80 C compilers'.  Although people here are advocating not using the C library, with z88dk I'd the encourage the opposite :)  If you want to investigate the quality of the implementation you can have a look at the source, here for the new C library which is what I think you'd need to use for the rc2014:   http://z88dk.cvs.sourceforge.net/viewvc/z88dk/z88dk/libsrc/_DEVELOPMENT/   (you'll have to navigate around the dead directories that cvs retains in the online code base)  It is true that using printf/scanf comes at a cost since it has to implement complex behaviours defined by the standard which may not be required but the new C library's implementation does allow the user to individually exclude specific % converters in order to at least help reduce code footprint.  And of course you don't have to use what you don't want to.


The new C library also allows you to define a new target and you could define one for the rc2014.  The advantage of doing this is you can cherry pick what goes into the rc2014 library as well as add rc2014-specific functionality.  You also get to supply one or more crts that would allow you to insert startup code for different compile scenarios (compile for rom, compile for ram, etc).  You can also supply defaults such as code origin, data origin, bss origin, heap size, and so on that make sense for the target.

Information on creating a new target for the new c library can be found here:  http://www.z88dk.org/wiki/doku.php?id=temp:front#creating_a_target    but it's probably better to scan the new c lib documentation on that page first:   http://www.z88dk.org/wiki/doku.php?id=temp:front


Installation instructions for z88dk here:  http://www.z88dk.org/wiki/doku.php?id=temp:front#installation   and I'd recommend using a nightly build rather than the last release.  z88dk is an active project and it changes quite quickly.  If you run on windows or mac there are binary packages available from the nightly build.  For linux or other targets there are instructions for building from source and for patching sdcc to create zsdcc, z88dk's version of sdcc.


Just to add for the ROM target:  the new C lib allows the stored data section to be lz77 compressed so this should save a few bytes in the stored binary in ROM.  Another thing you could do is compile a program for RAM and store a compressed copy in ROM that gets decompressed into RAM at startup.  If you only have 8k rom but 32k ram, this could allow programs twice as large as 8k to be stored in ROM and expanded in RAM before being run.  An example of this was done for a lisp interpretter that was destined for a 16k rom cartridge on the zx spectrum and is described here:   http://www.z88dk.org/wiki/doku.php?id=libnew:examples:clisp


Anyway I hope that's helpful.  The z88dk forums is another good source of information  http://www.z88dk.org/forum/forums.php

Filippo Bergamasco

unread,
Jun 8, 2016, 12:52:36 PM6/8/16
to rc201...@googlegroups.com
Hi,
I'm sorry if someone asked on your forum for the rc2014 :) this is a rather new community that is growing  around Spencer's RC2014 home-brew computer. 

First of all, I must say that z88dk is a very good toolkit. I've just started experimenting a bit in the attempt to port Scott's C basic implementation (originally designed for Arduinos) to our RC2014. I've read some of the documentation and I was aware of the multiple c and assembly compiler choice. 
Thank you for clarifying that there are also multiple C runtimes that can be used and suggesting me to have a look to the newest one.

Given the growing interest for rc2014, I was already thinking to handle it properly and create a new z88dk "target" for it. As usual, I'll need to provide some initialisation code and I need to figure out where to place all the stuff in memory (stack location, ROM/RAM areas, etc). I'll read the documentation you mentioned to experiment a bit.

As I said, I'm quite new to this so I already have a lot of question regarding your toolkit. If you think it's appropriate I can post some on your forum. In the meanwhile, thank you again for your all the useful informations you told us :)

Filippo



--
You received this message because you are subscribed to the Google Groups "RC2014-Z80" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rc2014-z80+...@googlegroups.com.
To post to this group, send email to rc201...@googlegroups.com.

A A

unread,
Jun 9, 2016, 4:14:16 AM6/9/16
to RC2014-Z80


On Wednesday, June 8, 2016 at 10:52:36 AM UTC-6, Filippo Bergamasco wrote:
I'm sorry if someone asked on your forum for the rc2014 :) this is a rather new community that is growing  around Spencer's RC2014 home-brew computer. 

It shows that people are interested :)

 
Given the growing interest for rc2014, I was already thinking to handle it properly and create a new z88dk "target" for it. As usual, I'll need to provide some initialisation code and I need to figure out where to place all the stuff in memory (stack location, ROM/RAM areas, etc). I'll read the documentation you mentioned to experiment a bit.

Yes I think that is the way to go.  If you get a target going, we can put it into the z88dk codebase.  Having it there means we're also aware of it so if changes occur we can make sure it continues to work.


As I said, I'm quite new to this so I already have a lot of question regarding your toolkit. If you think it's appropriate I can post some on your forum. In the meanwhile, thank you again for your all the useful informations you told us :)

You're welcome to post any time.  z88dk documentation is not complete:-  there is a lot missing and many things are only partially explained so we expect people will need help to get to grips with how things work.


Filippo Bergamasco

unread,
Jun 12, 2016, 6:53:37 AM6/12/16
to rc201...@googlegroups.com
Hey all,
Thanks to the cool guys at z88dk I've finally managed to compile the TinyBasicPlus considering our rom/ram layout... and the output is below 8k!

I've already tested it with the new Scott's emulator, and is working great! So please Spencer I'd want ask you if you can try on the real RC2014.
Just flash the rom (attached you'll find the hex or the binary file) and go!

Filippo



--
You received this message because you are subscribed to the Google Groups "RC2014-Z80" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rc2014-z80+...@googlegroups.com.
To post to this group, send email to rc201...@googlegroups.com.
TinyBasicPlus.hex
TinyBasicPlus.rom

Spencer Owen

unread,
Jun 12, 2016, 8:38:05 AM6/12/16
to rc201...@googlegroups.com

Nearly! It is very close to working, but not quite...

If I boot straight in to the Tiny Basic rom, I just get a line of random characters on the screen (although the same random characters every time).  Same if I reset it.

However, if I boot with the jumpers set to Grants Basic, then swap the jumpers around, it kind of works, and displays your copyright message. So this makes me think the reset routine isn't initialising the CPU properly?

If I type in a simple program, it gets confused. Sometimes it lists ok, and sometimes it has (consistently) random characters in it, but I can't get it to run. This might be because it had been "jump started" from Grants Basic though, so it might not have initialised what it needs.  See attached screen shots.

(I love the error messages btw)

Let me know if there's any more tests you want me to do if it'll help.

Thanks

Spencer 

Filippo Bergamasco

unread,
Jun 12, 2016, 10:10:55 AM6/12/16
to rc201...@googlegroups.com
mm.. that's strange. I mean, the character garbage is maybe due to the wrong ACIA chip initialisation but the fact that the program get "confused" may be a bigger problem (ie. i'm overwriting the stack or something similar).

Anyway, I've changed the ACIA initialisation a bit and moved the stack. If you can please check both TinyBasicPlus and the super simple pigfx_test you l'll do me a big favour. In the meanwhile... i really need to buy a cheap chinaware eeprom programmer

Filippo


TinyBasicPlus.hex
TinyBasicPlus.rom
pigfx_test.hex
pigfx_test.rom

Scott Lawrence

unread,
Jun 12, 2016, 10:12:43 AM6/12/16
to rc201...@googlegroups.com
I just misread "pigfx" as "pig-fix"

S

Sent from my fancy-schmancy phone.

For more options, visit https://groups.google.com/d/optout.
<TinyBasicPlus.hex>
<TinyBasicPlus.rom>
<pigfx_test.hex>
<pigfx_test.rom>

Filippo Bergamasco

unread,
Jun 12, 2016, 10:45:30 AM6/12/16
to rc201...@googlegroups.com
Haha well... It should be Pi (as in raspberry Pi) GFX (as in graphics) but to me often sounds like pig effects :)

- Filippo

Spencer Owen

unread,
Jun 12, 2016, 12:21:30 PM6/12/16
to rc201...@googlegroups.com

Ooops, maybe it's just me that inserts a silent U in PIGFX :-/

Yes! Tiny Basic works! See attached screenshots (including the broken one from earlier which I forgot to attach!).  I'll have to go and look up what the advantages and limitations of this one is now.

Also, see screenshot from the pigfx_test rom. It looks like it works - assuming that's what it us supposed to do :)

Cheers

Spencer

PhotoGrid_1465748407703.jpg

Filippo Bergamasco

unread,
Jun 12, 2016, 1:08:04 PM6/12/16
to rc201...@googlegroups.com
Oh yea!!! I'm so happy that it works!
Well, this TinyBasicPlus is more limited than the other, for instance there is no string support and a lot less build-in commands.

However... we achieved this remarkable points:

1 - we have a development environment that allows to seamlessly mix C and assembly. And is quite advanced! It automatically copies the data and bss section to ram and offers a lot of useful functions in the standard library (even floating point arithmetics)

2 - we have a basic interpreter completely written in C, easy to maintain and to expand (I've already added INK and PAPER commands as you asked me long time ago)

3 - we have a good emulator that is quite close to the actual hardware.

4 - I am on the go to provide also a graphic library to easily interface the card without that cumbersome ansi codes


All these points sound quite attractive to someone who wants to experiment with homebrew computers... Thus, you will probably become rich :P

- Filippo

> On 12 giu 2016, at 18:21, Spencer Owen <spe...@sowen.com> wrote:
>
> <PhotoGrid_1465748407703.jpg>

phillip.stevens

unread,
Jun 12, 2016, 10:39:59 PM6/12/16
to RC2014-Z80
Also confirmed to work by programming with the TL-866.
I loaded the code into the TL-866 buffer to start at 0x2000, and requested it to ignore programming 0xFFs.

That allows me to have both basic versions on the EEPROM at the same time.
  • MicrosoftBasic from 0x0000
  • TinyBasic from 0x2000
Moving the jumper to select which version boots.

[Off topic: I've been learning CUPL to drive PLDs]


On Monday, 13 June 2016 03:08:04 UTC+10, Filippo Bergamasco wrote:
Oh yea!!! I'm so happy that it works!

phillip.stevens

unread,
Jun 12, 2016, 10:48:48 PM6/12/16
to RC2014-Z80
Fillippo,

off topic but your HEX file is missing the first line, and this causes my programmer TL-866 to hickup.

It should start off with a line like this [:20xxxxx.....]

:020000040000FA

but your HEX omits this line, and goes straight to content [:10xxxxx....]
.
The picture below shows how the Greg Searle Microsoft Basic HEX looks (note this is just a picture from the net. i can't read the caption)



Is that a configuration issue with your linker?

Best regards, Phillip

Filippo Bergamasco

unread,
Jun 13, 2016, 2:50:46 AM6/13/16
to rc201...@googlegroups.com
Hey Phillip,
Nice to hear that it works also for you. As you see is still in an early development stage but it will grow.
The linker output is directly a binary blob, I then use a tool to convert binary data to hex given the base address.

The fist line you mentioned is not mandatory (does not contain any program data) but strangely still causes trouble with your programmer.
The important thing is not that it starts with :20 but the 04 at the 4th byte position. This tells that we want to use 32bit addressing mode and the subsequent 2 bytes (00 00 in our case) are to be considered as the upper 16bit of all subsequent addresses in the file. It may be the case that your programmer requires a 32bit addressing mode and cannot continue without knowing what to put in the upper 16bits.
It is safe to just copy-paste that first line to my hex. But is not properly a bug.

- Filippo
--
You received this message because you are subscribed to the Google Groups "RC2014-Z80" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rc2014-z80+...@googlegroups.com.
To post to this group, send email to rc201...@googlegroups.com.

phillip.stevens

unread,
Jun 13, 2016, 3:32:13 AM6/13/16
to RC2014-Z80
Yes, I agree that is the issue. I think the TL-866 needs to be notified about 32bit mode, because it was complaining about the 0x02 byte (the third byte) of the content being wrong when writing.

So yes, it does make sense to just past the first line above the contents following, to stop the hiccup.

:020000040000FA

Filippo Bergamasco

unread,
Jun 13, 2016, 3:49:10 AM6/13/16
to rc201...@googlegroups.com
I can paste it automatically when i generate the hex if this somehow increases the compatibility with various programmers. Spencer, you confirm that if we past that line at the top you are still able to program?



--
You received this message because you are subscribed to the Google Groups "RC2014-Z80" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rc2014-z80+...@googlegroups.com.
To post to this group, send email to rc201...@googlegroups.com.

Spencer Owen

unread,
Jun 13, 2016, 5:27:16 AM6/13/16
to rc201...@googlegroups.com
I used the .rom files to program, not the .hex so I don't know if the lack of or the addition of that line will affect it on the Willem programmer.

I'll check it when I get back tonight and let you know.

Cheers

Spencer

phillip.stevens

unread,
Sep 18, 2016, 3:29:32 AM9/18/16
to RC2014-Z80
Its been a long break between drinks, but finally I've built a RC2014 target for z88dk.
The repository is a snapshot of z88dk from a few nights ago, with a patched SDCC 3.6.3.

I've checked that the code generated seems to be correct, by reading the decompiled binary.
Testing on the RC2014 hardware comes next.

I've been writing up my process here. Obviously, I'm still not finished with documentation...

The commands to build a Intel HEX program from test.c using the rc2014 target look like this:

zcc +rc2014 -v -SO3 -startup=1 -clib=sdcc_iy --max-allocs-per-node200000 --c-code-in-asm --list -m test.c -o test
cat test_CODE
.bin test_DATA.bin > test.bin
z80dasm
--address --labels --origin=0x0 test.bin

appmake
+hex --org=0x0 --binfile test.bin --output test.hex

I'm still at this so consider it a snapshot of a work in progress.

Cheers, Phillip

phillip.stevens

unread,
Sep 18, 2016, 3:37:53 AM9/18/16
to RC2014-Z80
BTW Here's the nonsensical test.c which I've been using to test sanity of interrupt locations, critical sections, and code generation.

#include <stdio.h>

extern uint8_t rc2014_pollc(void);
extern uint8_t rc2014_getc(void);
extern void rc2014_putc(uint8_t);

uint8_t a
;
uint8_t chr
;

void main(void)
{
 
for(;;)
 
{
 
if (rc2014_pollc())
 
{
 chr
= rc2014_getc();
 rc2014_putc
(chr);
 
}
 
 __critical
{
 a
++;
 
}
   
}
}


void z80_rst_38h (void) __interrupt (1)
{
 a
--;
}

Filippo Bergamasco

unread,
Sep 19, 2016, 3:00:57 AM9/19/16
to rc201...@googlegroups.com
Hey Phillip,
you did a great job!  It was since I started messing around with z88dk that I wanted to setup everything properly by creating a new target. Is it supported both the ROM+RAM and RAM only memory model?

Once everything is tested and running, it would be great to change the build scripts inside https://github.com/RC2014Z80/RC2014/tree/master/ROMs so that they can use the new taget instead of the generic "embedded". If you want to do that, you can either create a new pull request or ask me for repository access.

Filippo


--
You received this message because you are subscribed to the Google Groups "RC2014-Z80" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rc2014-z80+unsubscribe@googlegroups.com.

To post to this group, send email to rc201...@googlegroups.com.

phillip.stevens

unread,
Sep 19, 2016, 3:22:54 AM9/19/16
to RC2014-Z80
Filippo,

I think most things are set up properly, and should permit any memory model structure to be built going forward. Extending the build configuration to cover lots of options (with
-startup=x
where x is from 0 to n)  is one of the z88dk strengths.

Part of the time effort was to understand the structure of the z88dk compilation process, to ensure that their standard process is doing most of the heavy lifting, and only the hardware special items are included for each target.

If you look at the very slight differences between the +embedded target and the +rc2014 target, you can see that it is really only the existence of an interrupt list, and inserting reference to this file in the crt_0 build instructions. The interrupts are basically your USART enabling code.

The way I've done the interrupts (which I hope is a relatively standard way) means that it is easy to add and remove interrupts, and also to declare these interrupts in C or asm at any point in your code.

I'll be testing against hardware tonight, which might bring some more fixes for things I've done wrong.

The real work ongoing is to build drivers to enable the use of the stdio.h capabilities on the USART. I'll be looking at this over the next few days, and also building an interrupt driven Tx and Rx based USART management. If I can't find an existing am version of interrupt driven USARTS, I'll be trying to port my ATmega solution over, because that is a known working input.

Once I've got a properly working version, RC2014Z80 Github group can clone my z88dk repo, and it can become the master repo then. But I woudn't do that until I'm sure things are in a good state.

Cheers, Phillip

phillip.stevens

unread,
Sep 19, 2016, 7:10:15 AM9/19/16
to RC2014-Z80


Well the good news is with the nonsensical mini test program, that there is life!

But it is not correct. Ideally the characters should be echoed. They aren't being echoed.



But, I'm afraid I won't be able to do any more debugging currently.

I've just been made aware that the 27C512 ROM is just that, ROM.


Silly me thought it was EEPROM...

And, I've run out of lives.


Stay tuned. ;-)

Filippo Bergamasco

unread,
Sep 19, 2016, 7:38:40 AM9/19/16
to rc201...@googlegroups.com
Have you tried with the emulator?

phillip.stevens

unread,
Sep 19, 2016, 9:24:48 PM9/19/16
to RC2014-Z80
No, I haven't tried an emulator.
It looks like that is the only way to do zero $ development is via an emulator. I can't find an EEPROM device that is as fast as the PROM 27C512.
The only thing that is close is a 28C64, but that is 150ns (not 45ns).

What do you suggest?

Filippo Bergamasco

unread,
Sep 20, 2016, 3:54:15 AM9/20/16
to rc201...@googlegroups.com
Hey Philip,
the emulator is actually quite usable, especially if you need to test interrupts. Also, it's  the only way to debug step-by-step and inspect memory areas. Most of the stuff I developed so far was made with the emulator.

Additionally you can use the basic boot loader you can find here:


to upload your code. Of course you cannot overwrite the interrupt vector area since it's in the lower ROM memory area. 

Filippo


Johannes Krampf

unread,
Sep 20, 2016, 5:18:36 PM9/20/16
to rc201...@googlegroups.com
On 09/20/2016 03:24 AM, phillip.stevens wrote:
> No, I haven't tried an emulator.
> It looks like that is the only way to do zero $ development is via an
> emulator. I can't find an EEPROM device that is as fast as the PROM 27C512.
> The only thing that is close is a 28C64, but that is 150ns (not 45ns).
>
> What do you suggest?

Do you think the W27C512-45Z would work here?

Datasheet:
https://media.digikey.com/pdf/Data%20Sheets/Winbond%20PDFs/W27C512.pdf
(Linked from
https://www.digikey.com/product-detail/en/winbond-electronics/W27C512-45Z/W27C512-45Z-ND/1133334
)

I couldn't find the part from a well-known seller, but there are ~100
offers from China or Hong Kong on ebay with wildly varying prices. I
don't know if there are fakes and how to spot genuine articles, though.

I realise that it's not sold any more, but
https://www.tindie.com/products/UnaClocker/28cxxx-eeprom-programmer-shield-for-arduino/
should allow programming. Schematics are linked on the page. It is said
to work with the related (renamed?) W27E512 which also uses 14V for
erase and 12V for programming. I couldn't find W27E512 in the 45ns
variant on offer anywhere.

Big disclaimer: I have no practical experience with (E)EPROMs. All
information above comes from searches.

P.S. I'd love to be able to write C for the RC2014!

A A

unread,
Sep 23, 2016, 2:20:57 PM9/23/16
to RC2014-Z80


On Sunday, September 18, 2016 at 1:37:53 AM UTC-6, phillip.stevens wrote:


void z80_rst_38h (void) __interrupt (1)
{
 a
--;
}



There is a problem here with the way the interrupt routine is declared -- you're getting a "im2 isr with prioritized daisy chain" isr which re-enables interrupts immediately on entry so you would have had interrupts enabled while inside the isr.  You could probably see that from your .lis file (--list) or if you stop at assembly translation (-a).

I think what you probably wanted is:

void z80_rst_38h (void) __critical __interrupt(0)
{
 a--;
}

You can find some documentation on this in the second table under this section:
http://www.z88dk.org/wiki/doku.php?id=libnew:target_embedded#implementing_the_z80_restarts_im1_and_nmi 

zcc +rc2014 -v -SO3 -startup=1 -clib=sdcc_iy --max-allocs-per-node200000 --c-code-in-asm --list -m test.c -o test
cat test_CODE
.bin test_DATA.bin > test.bin
z80dasm
--address --labels --origin=0x0 test.bin

appmake
+hex --org=0x0 --binfile test.bin --output test.hex


In your rc2014.cfg file if you add to OPTIONS "-subtype=default" and the line "SUBTYPE  default -Cz+rom" then you can get z88dk to create the final binary for you if you add "-create-app" to the compile line.  (You can refer to the embedded.cfg file there to see where this options stuff goes).  This gives zcc information about how appmake should be invoked to create a final binary.

With that in place the above compile line becomes:

zcc +rc2014 -v -SO3 -startup=1 -clib=sdcc_iy --max-allocs-per-node200000 --c-code-in-asm --list -m test.c -o test -create-app -Cz--ihex
z80dasm --address --labels --origin=0x0 test.bin

You will get the final binary "test.bin" (which has had the cat done already) and "test.ihx" with correct ORG set from the compile.  create-app will work for all the compile models, including the compressed rom model in which create-app will also compress the data section for you.

A A

unread,
Sep 23, 2016, 3:18:36 PM9/23/16
to RC2014-Z80


On Monday, September 19, 2016 at 5:10:15 AM UTC-6, phillip.stevens wrote:


Well the good news is with the nonsensical mini test program, that there is life!

But it is not correct. Ideally the characters should be echoed. They aren't being echoed.



I think I will save you some grief :)

 In your test program rc2014_putc() is declared like this:

extern void rc2014_putc(uint8_t);

And your source code implementation in asm is this:

PUBLIC _rc2014_putc
_rc2014_putc:
ld a, l
rst $08 ; jump to Interrupt Address 0x0008
ret

The C declaration you gave is telling the compiler to use standard linkage when calling your routine, that is it's going to push the char parameter onto the stack.  The code generated for the call looks like this:

    ld    a,l   ;; this is the char value to be printed
    ld    (_chr),a   ;; another statment is storing to a variable
    push    af   ;; push one byte parameter onto stack
    inc    sp
    call    _rc2014_putc  ;; call function
    inc    sp   ;; repair stack

Your asm implementation wants the char sent via register, not the stack.  So you must tell the compiler to use fastcall linkage when calling your routine.  fastcall linkage means send one parameter via a subset of registers dehl.  It will be in L this case since the param sent is just 8-bits.

So you may have better luck if you change your C prototype to:

extern void rc2014_putc(uint8_t) __z88dk_fastcall;

The other prototypes are fine since they don't take any parameters.  Return values are always in a subset of dehl (well, except for floats and 64-bit longlong but it's not common to have to worry about that).

phillip.stevens

unread,
Sep 23, 2016, 9:15:40 PM9/23/16
to RC2014-Z80
Thanks for the clarifications. I've got some more questions, but I'll split them into separate posts so the thread can be easily followed.

Interrupts.

Is the issue around provision of the __critical keyword? 

I would have thought that the code would need to refer to interrupt 1 as in

void z80_rst_38h (void) __critical __interrupt(1)
{
 a
--;
}

The table you refer to is not specific as to whether the bracketed variable needs to match, or not.
Or, is it always  __critical __interrupt(0)  ?

If so, then OK... ;-)

phillip.stevens

unread,
Sep 23, 2016, 9:22:52 PM9/23/16
to RC2014-Z80
Thanks that's good to know.

The documentation refers to the appmake tool only being able to be run in default mode from within the standard chain, and separating it out from the compilation was the only way that I could see to get a HEX file as output.

Is the .ihx file you refer to in standard HEX format with preamble, etc?
I'll give this a go shortly.

A A

unread,
Sep 24, 2016, 12:38:30 AM9/24/16
to RC2014-Z80


On Friday, September 23, 2016 at 7:15:40 PM UTC-6, phillip.stevens wrote:

Interrupts.

Is the issue around provision of the __critical keyword? 

I would have thought that the code would need to refer to interrupt 1 as in

void z80_rst_38h (void) __critical __interrupt(1)
{
 a
--;
}

The table you refer to is not specific as to whether the bracketed variable needs to match, or not.
Or, is it always  __critical __interrupt(0)  ?


Yes, it's always __critical __interrupt(0).  The number doesn't mean anything other than to act as differentiation to the compiler.

These are keywords reused by sdcc for interrupt related things across a lot of different CPUs so the devs are just bending the keywords to the z80's interrupt structure.  Somehow you have to identify an nmi isr, a regular maskable isr (with ei; reti at the end) and a special im2 daisy chain isr (with ei at the beginning and reti at the end).  Then there is also the possibility that you could have the compiler generate an im2 vector table.  The numbers in brackets could be used as ID byte (and vector table index) but this has not been done.  Any im2 vector table has to be manually created.

So yeah, I would agree the syntax seems a bit odd :)

A A

unread,
Sep 24, 2016, 1:07:41 AM9/24/16
to RC2014-Z80


On Friday, September 23, 2016 at 7:22:52 PM UTC-6, phillip.stevens wrote:
 
The documentation refers to the appmake tool only being able to be run in default mode from within the standard chain, and separating it out from the compilation was the only way that I could see to get a HEX file as output.

 
The default mode(s) are chosen by the person who makes the target's cfg file.

When -create-app is added to the compile, zcc will run "appmake -b outputfilename -c targetcrt [arguments]" where arguments are supplied from the target cfg file and the compile line.  From the -b, appmake will know what the output files are and from -c appmake will find compile information like ORG address.  The rest, like what flavour of appmake to use and any additional options for that flavour are supplied by the cfg file or the user.

So the suggestion I made above added "-subtype=default" to OPTIONS and another line "SUBTYPE  default -Cz+rom".  Anything added to the OPTIONS line is added to every compile line so the result of that is every compile will have "-subtype=default" added.  When zcc processes the cfg file it interprets "subtye=default" to mean add any options found on the "SUBTYPE default" line.  So every compile will now get "-Cz+rom".  -Cz passes the argument to appmake.  If -create-app is added to the compile line, the invocation of appmake will now be:

appmake -b outputfilename -c targetcrt +rom

So the output binaries will be processed by "appmake +rom".  If you enter "appmake +rom" on the command line you'll see a list of options that it accepts.  You could add more of those options as defaults to the "SUBTYPE default" line in the cfg file if you thought that was appropriate.  You could add -Cz--ihex to always get an ihx file, for example. If the user adds -Cz options to the compile line, they will be sent to appmake +rom too.

You could add other appmake defaults to the cfg file.  The user can change the selected appmake default by adding "-subtype=????" on the compile line.  This overrides the default of "-subtype=default" in the OPTIONS text.  Another line in the cfg file with "SUBTYPE ???" can be used to select a different appmake invocation with -create-app.  The zx target is an example of this.  It has two output types, one being a tape file and the other being a rom cartridge and these are selectable via subtype.

The short of it is, you get to decide how appmake is invoked in the target's cfg file.

Is the .ihx file you refer to in standard HEX format with preamble, etc?
I'll give this a go shortly.

Yes that's an intel hex file.  It's the same file as generated by appmake +hex

 

phillip.stevens

unread,
Sep 26, 2016, 5:58:39 AM9/26/16
to RC2014-Z80
Thanks. Yes, I think I've got it now.
It becomes straight forward when it is explained in detail. ;-)

the rc2014.cfg file updates are now committed to the repo.

OPTIONS  -v -O2 -SO2 -I. -D__Z88DK -DZ80 -DRC2014 -D__RC2014__ -D__RC2014 -M -subtype=rom -clib=sdcc_iy

CLIB     sdcc_iy
-compiler=sdcc --reserve-regs-iy -D__SDCC -D__SDCC_IY -Ca-D__SDCC -Ca-D__SDCC_IY -Cl-D__SDCC -Cl-D__SDCC_IY -nostdlib -IDESTDIR/include/_DEVELOPMENT/sdcc -Ca-IDESTDIR/libsrc/_DEVELOPMENT/target/rc2014 -lrc2014 -
crt0
=DESTDIR/libsrc/_DEVELOPMENT/target/rc2014/rc2014_crt

SUBTYPE   rom    
-startup=1 -Cz+rom -Cz--org=0 -Cz--ihex
SUBTYPE   rom_zx7
-startup=2 -Cz+rom -Cz--org=0 -Cz--ihex

I've pasted the relevant changes here.

-subtype=rom instead of -subtype=default

-clib=sdcc_iy since this is the best library compiler option, so sensible to make it default

-subtype=rom_zx7 to indicate compressed rom through having -startup=2

I think that has pretty much finished a working tool-set for the rc2014.
Now I just have to write an interface between the standard I/O libraries, and the rc2014 68B50 USART
I'm guessing that will be easy to copy from somewhere.

When I get that code working, after I get the emulator working, this configuration should make ihex code generation really easy.

Thanks again.


On Saturday, 24 September 2016 15:07:41 UTC+10, A A wrote:


On Friday, September 23, 2016 at 7:22:52 PM UTC-6, phillip.stevens wrote:
 
The documentation refers to the appmake tool only being able to be run in default mode from within the standard chain, and separating it out from the compilation was the only way that I could see to get a HEX file as output.

phillip.stevens

unread,
Sep 26, 2016, 6:02:27 AM9/26/16
to RC2014-Z80
I should add some example command lines to use this configuration

zcc +rc2014 -subtype=rom --max-allocs-per-node200000 test.c -o test -create-app
or
zcc +rc2014 -subtype=rom_zx7 --max-allocs-per-node200000 test.c -o test -create-app
or
zcc +rc2014 -subtype=rom -v -m -SO3 --max-allocs-per-node200000 --c-code-in-asm --list test.c -o test -create-app

A A

unread,
Sep 27, 2016, 9:07:10 PM9/27/16
to RC2014-Z80


On Monday, September 26, 2016 at 3:58:39 AM UTC-6, phillip.stevens wrote:

OPTIONS  -v -O2 -SO2 -I. -D__Z88DK -DZ80 -DRC2014 -D__RC2014__ -D__RC2014 -M -subtype=rom -clib=sdcc_iy

CLIB     sdcc_iy
-compiler=sdcc --reserve-regs-iy -D__SDCC -D__SDCC_IY -Ca-D__SDCC -Ca-D__SDCC_IY -Cl-D__SDCC -Cl-D__SDCC_IY -nostdlib -IDESTDIR/include/_DEVELOPMENT/sdcc -Ca-IDESTDIR/libsrc/_DEVELOPMENT/target/rc2014 -lrc2014 -
crt0
=DESTDIR/libsrc/_DEVELOPMENT/target/rc2014/rc2014_crt

SUBTYPE   rom    
-startup=1 -Cz+rom -Cz--org=0 -Cz--ihex
SUBTYPE   rom_zx7
-startup=2 -Cz+rom -Cz--org=0 -Cz--ihex

 
That's a pretty good idea to use a more friendly subtype=rom,... to shorten the compile line.

I would take out the "-Cz--org=0" though -- appmake will pick up the org address from CRT_ORG_CODE after the compile so it will use your target default of 0x0000 anyway.  If you leave it in there, it will override what the user supplies as ORG.  So, eg:

zcc +rc2014 -subtype=rom --max-allocs-per-node200000 test.c -o test -create-app -pragma-define:CRT_ORG_CODE=0x100

should be able to move the org address to 0x100 and it will do that if ""-Cz--org=0" is removed.

I'd also add a RAM model compile maybe with subtype=ram.  There is a hex loader that loads programs to a fixed ram address so the default compile address could be that.

Maybe subtype=romc is quicker to type than subtype=rom_zx7 ?

In target defaults you're selecting a cmos z80 and enabling "sll r" instructions.  Mainly the selection of cmos means the library will use "ld a,i" to find out if maskable interrupts are currently enabled.  This works reliably on cmos z80s only and will occasionally fail on nmos z80s.  "sll r" type instructions may not be reliable on cmos z80s (they are undocumented and are replaced in the z80's successor chips).

In "crt_interrupts.inc", I would move those subroutines into the rc2014 library.  What will happen is if any of those restarts are enabled (and one is by default - CRT_ENABLE_RST = 0x8E) and the program does not supply them, the linker will find them in the library.  But if the user program supplies them, the user's versions will override the library versions because the linker will preferentially search the user code for the restarts first.  So if those routines are moved to the library, the user program has the ability to supply its own if that's what the user wants.  If they are moved to the library, I would assign the code to section "code_crt_common".  This will ensure the code is sequenced right after the crt startup code very early in the binary.  In case someone tries bankswitching this will keep the restart code in an unbankswitched memory page.

So far so good :)  The hookup to stdio comes with a bit of learning curve and lack of documentation but the cp/m drivers connected to bdos (not the dcio version as those are plugging into z88dk's terminal drivers) are simply outputting and reading single chars which I think should be very similar to how an rc2014 driver might look.  If you are doing an interrupt driven version, you would have to write code that writes incoming chars to a circular buffer and then the driver would read from that circular buffer.  There are a bunch of containers in the adt subdirectories but none are circular buffers.

phillip.stevens

unread,
Sep 27, 2016, 9:27:30 PM9/27/16
to RC2014-Z80


On Wednesday, 28 September 2016 11:07:10 UTC+10, A A wrote:


On Monday, September 26, 2016 at 3:58:39 AM UTC-6, phillip.stevens wrote:

OPTIONS  -v -O2 -SO2 -I. -D__Z88DK -DZ80 -DRC2014 -D__RC2014__ -D__RC2014 -M -subtype=rom -clib=sdcc_iy

CLIB     sdcc_iy
-compiler=sdcc --reserve-regs-iy -D__SDCC -D__SDCC_IY -Ca-D__SDCC -Ca-D__SDCC_IY -Cl-D__SDCC -Cl-D__SDCC_IY -nostdlib -IDESTDIR/include/_DEVELOPMENT/sdcc -Ca-IDESTDIR/libsrc/_DEVELOPMENT/target/rc2014 -lrc2014 -
crt0
=DESTDIR/libsrc/_DEVELOPMENT/target/rc2014/rc2014_crt

SUBTYPE   rom    
-startup=1 -Cz+rom -Cz--org=0 -Cz--ihex
SUBTYPE   rom_zx7
-startup=2 -Cz+rom -Cz--org=0 -Cz--ihex

 
That's a pretty good idea to use a more friendly subtype=rom,... to shorten the compile line.

I would take out the "-Cz--org=0" though -- appmake will pick up the org address from CRT_ORG_CODE after the compile so it will use your target default of 0x0000 anyway.  If you leave it in there, it will override what the user supplies as ORG.  So, eg:

zcc +rc2014 -subtype=rom --max-allocs-per-node200000 test.c -o test -create-app -pragma-define:CRT_ORG_CODE=0x100

should be able to move the org address to 0x100 and it will do that if ""-Cz--org=0" is removed.

Ok I'll remove the origin statement, since it defeats an option for users, that defaults to what I'd want anyway.


I'd also add a RAM model compile maybe with subtype=ram.  There is a hex loader that loads programs to a fixed ram address so the default compile address could be that.

Not sure what the characteristics of a RAM load would look like right now. Either the existing hardware starting at 0x8000 or a new hardware starting at 0x2000. Depends on what Spencer comes up with. I'll leave this for the time being.
 
Maybe subtype=romc is quicker to type than subtype=rom_zx7 ?

Yes, but not as clear... I'm a bit OCD about clarity.
 

In target defaults you're selecting a cmos z80 and enabling "sll r" instructions.  Mainly the selection of cmos means the library will use "ld a,i" to find out if maskable interrupts are currently enabled.  This works reliably on cmos z80s only and will occasionally fail on nmos z80s.  "sll r" type instructions may not be reliable on cmos z80s (they are undocumented and are replaced in the z80's successor chips).

I assumed that we're only dealing with CMOS (static) chips these days? The rc2014 comes so equipped. Is there any reason to believe that we have to deal with NMOS Z80s in the rc2014 target? 

In "crt_interrupts.inc", I would move those subroutines into the rc2014 library.  What will happen is if any of those restarts are enabled (and one is by default - CRT_ENABLE_RST = 0x8E) and the program does not supply them, the linker will find them in the library.  But if the user program supplies them, the user's versions will override the library versions because the linker will preferentially search the user code for the restarts first.  So if those routines are moved to the library, the user program has the ability to supply its own if that's what the user wants.  If they are moved to the library, I would assign the code to section "code_crt_common".  This will ensure the code is sequenced right after the crt startup code very early in the binary.  In case someone tries bankswitching this will keep the restart code in an unbankswitched memory page.

Yes. Absolutely. I'm thining about how to do the hookup to stdio and am in two minds about it. I still haven't reached an agreement with myself.

  • Option a). Move everything in a C library for stdio interrupts, where I have access to a fast ring buffer implementation that I understand. And leave just the minimum start-up code in ASM. That would be setting the stack, heap, configuring interrupt IM 1. This is like the way I do AVR ATmega code.
  • Option b). Keep the stdio routines for putc and getc characters to USART in ASM. There it would be a longer learning experience with Z80 assembler. But that's part of the fun. But on the upside it may also be easier to link to the ASM routines for stdio if the putc and getc routines are also in ASM.


So far so good :)  The hookup to stdio comes with a bit of learning curve and lack of documentation but the cp/m drivers connected to bdos (not the dcio version as those are plugging into z88dk's terminal drivers) are simply outputting and reading single chars which I think should be very similar to how an rc2014 driver might look.  If you are doing an interrupt driven version, you would have to write code that writes incoming chars to a circular buffer and then the driver would read from that circular buffer.  There are a bunch of containers in the adt subdirectories but none are circular buffers.

 
Thanks for the tips on stdio. The documentation on integrating the stdio comes from the classic library and was last updated in 2003. I wasn't sure that it was still accurate. It would be good to have some current info on this. 

 

A A

unread,
Sep 27, 2016, 10:56:37 PM9/27/16
to RC2014-Z80


On Tuesday, September 27, 2016 at 7:27:30 PM UTC-6, phillip.stevens wrote:

I'd also add a RAM model compile maybe with subtype=ram.  There is a hex loader that loads programs to a fixed ram address so the default compile address could be that.

Not sure what the characteristics of a RAM load would look like right now. Either the existing hardware starting at 0x8000 or a new hardware starting at 0x2000. Depends on what Spencer comes up with. I'll leave this for the time being.
 

One of the main issues people seem to have is that you have to program a ROM to get a compiled program onto the rc2014.  The ram model compile would let people run programs without having to program any roms.

 
Maybe subtype=romc is quicker to type than subtype=rom_zx7 ?

Yes, but not as clear... I'm a bit OCD about clarity.
 

Fari enough.  I was thinking "compressed rom" or "rom compressed" for romc but whatever works will do :)

I assumed that we're only dealing with CMOS (static) chips these days? The rc2014 comes so equipped. Is there any reason to believe that we have to deal with NMOS Z80s in the rc2014 target? 

I was thinking more of the enabling of "sll r" instructions -- these might not work on all cmos z80s.  We're careful to keep them out of most library code but some code comes from 3rd parties, one being zx7's decompressor.  It uses "sll r" instructions that we kept but provided a way to opt out of with that flag.
 

  • Option a). Move everything in a C library for stdio interrupts, where I have access to a fast ring buffer implementation that I understand. And leave just the minimum start-up code in ASM. That would be setting the stack, heap, configuring interrupt IM 1. This is like the way I do AVR ATmega code.
  • Option b). Keep the stdio routines for putc and getc characters to USART in ASM. There it would be a longer learning experience with Z80 assembler. But that's part of the fun. But on the upside it may also be easier to link to the ASM routines for stdio if the putc and getc routines are also in ASM.

Of course ideally it's in asm because there are probably a lot fewer cycles available on a z80 than an ATmega.  But the C would work and would enable something to get going quickly if the driver is not trivial.  Interfacing the C and asm is fairly simple -- you'd just need to be careful to save registers when passing to C.  You could always rewrite in asm as time passes.


Thanks for the tips on stdio. The documentation on integrating the stdio comes from the classic library and was last updated in 2003. I wasn't sure that it was still accurate. It would be good to have some current info on this. 

The new c library stdio model is completely different.  It's an object oriented model that is meant to simplify driver implementation by allowing code inheritance from the library, if that's desired.  It also allows the library to behave like it does on larger machines.  The difficulty is the new stdio model is not complete.  The disk i/o is yet to come and that will cause some changes in the rest of the drivers and the rest of the drivers are going to see a version 3 to clean up data structures and introduce non-blocking i/o.  So things are little bit fluid but the good news is, unless you want to go bare metal, it is possible to write terminal drivers by supplying a couple dozen lines of code to the current codebase.  Once you understand how to do it of course!  And your case of doing something interrupt driven will also mean more custom code has to be written by you but it would be fairly easy (in numbers of lines of code) to do a blocking driver now.

There is a short bit of information on the new c lib drivers here:
http://www.z88dk.org/wiki/doku.php?id=temp:front#device_drivers

The most important bit of information is the table of stdio messages.  Put that together with an example implementation and things will become clearer.  An example of a simple driver that just takes a single char from stdio and outputs it can be seen in:

target/cpm/driver/terminal/cpm_00_output_cons.asm

That driver implements one message and that's it.  The message function is in "target/cpm/driver/terminal/cpm_00_output_cons/cpm_00_output_cons_ochar_msg_putc.asm".  That function is 5 lines, passing the char to output to cp/m's bdos.  The comments at the top of the function tell how registers are set up on entry, what is required on exit and what registers you are allowed to modify (others must be saved on the stack).

The message the function implements is not one of the stdio messages generated by the library.  Instead, "OCHAR_MSG_PUTC" is a message generated by a library base class "character_00_output" whose implementation you will find in "drivers/character/character_00/output/character_00_output.asm"  You can investigate that source code to see how the base class handles stdio's messages and generates that one message the cp/m driver implements.

After that there is the m4 macro that needs to be created.  The macro will be instantiated in the crt to reserve any memory it needs for the driver.  Most of it will be magic bytes to set up the data structures needed by the library driver and the cp/m driver adds nothing to what the library demands.  But if your driver needs more memory (for example for a buffer), then it should be added to the m4 macro.

Anyway that should get you started.  Quick stdin/stdout/stderr drivers could be written by simply copying the cp/m bdos drivers.

This is something we don't expect people to be able to do without assistance so if you run into trouble just ask.

phillip.stevens

unread,
Sep 30, 2016, 10:30:43 AM9/30/16
to RC2014-Z80


I assumed that we're only dealing with CMOS (static) chips these days? The rc2014 comes so equipped. Is there any reason to believe that we have to deal with NMOS Z80s in the rc2014 target? 

I was thinking more of the enabling of "sll r" instructions -- these might not work on all cmos z80s.  We're careful to keep them out of most library code but some code comes from 3rd parties, one being zx7's decompressor.  It uses "sll r" instructions that we kept but provided a way to opt out of with that flag.

The BleuLlama emulator doesn't like these `sll r` instructions, so I've configured them out. It was for the best anyway.

  • Option b). Keep the stdio routines for putc and getc characters to USART in ASM. There it would be a longer learning experience with Z80 assembler. But that's part of the fun. But on the upside it may also be easier to link to the ASM routines for stdio if the putc and getc routines are also in ASM.

Of course ideally it's in asm because there are probably a lot fewer cycles available on a z80 than an ATmega.  But the C would work and would enable something to get going quickly if the driver is not trivial.  Interfacing the C and asm is fairly simple -- you'd just need to be careful to save registers when passing to C.  You could always rewrite in asm as time passes.

I've written an interrupt driven ring-buffered Tx and Rx character driver in ASM, which can then be linked to `Printf` style stdio in "c" code pretty easily. On the  BleuLlama emulator the Tx interrupt doesn't seem to be working for me, or there's something odd in my code (highly likely answer). Characters only come out, when other characters are going in... I'll be looking at this again soon.

Thanks for the tips on stdio. The documentation on integrating the stdio comes from the classic library and was last updated in 2003. I wasn't sure that it was still accurate. It would be good to have some current info on this. 

The new c library stdio model is completely different.  It's an object oriented model that is meant to simplify driver implementation by allowing code inheritance from the library, if that's desired.  It also allows the library to behave like it does on larger machines.  The difficulty is the new stdio model is not complete.  The disk i/o is yet to come and that will cause some changes in the rest of the drivers and the rest of the drivers are going to see a version 3 to clean up data structures and introduce non-blocking i/o.  So things are little bit fluid but the good news is, unless you want to go bare metal, it is possible to write terminal drivers by supplying a couple dozen lines of code to the current codebase.  Once you understand how to do it of course!  And your case of doing something interrupt driven will also mean more custom code has to be written by you but it would be fairly easy (in numbers of lines of code) to do a blocking driver now.

There is a short bit of information on the new c lib drivers here:
http://www.z88dk.org/wiki/doku.php?id=temp:front#device_drivers
 
Anyway that should get you started.  Quick stdin/stdout/stderr drivers could be written by simply copying the cp/m bdos drivers.
This is something we don't expect people to be able to do without assistance so if you run into trouble just ask.

Ah, after spending a few hours trying to understand how the "correct" method for building the right low level drivers for the stdio library, I'm still at a complete loss. I would guess that the right way is to move the things I've just written into a library somewhere, and call them by the correct names (with public definitions). But beyond that it seems very confusing for me. Sorry for being thick.

Any further clarity on the magic of standard library creation would be of great help.
Thanks in advance.

The test program for reference, since it is not in the repository.

#include <stdio.h>
#include <string.h>

extern void rc2014_init_acia(void);     // initialise the ACIA, and Interrupt
extern void rc2014_flush(void);         // initialise the Tx & Rx buffers
extern uint8_t rc2014_pollc(void);      // Rx polling routine, checks Rx buffer, not the ACIA
extern uint8_t rc2014_getc(void);       // Rx receive routine, from Rx buffer
extern uint8_t rc2014_peekc(void);      // Rx peek routine, reads Rx without removing it from buffer
extern void rc2014_putc(uint8_t) __z88dk_fastcall; // Tx write routine, writes to Tx buffer

void rc2014_Print( const uint8_t * str);// print out strings

uint8_t chr
;

void main(void)
{
  rc2014_init_acia
();

  rc2014_Print
("Hello World\r\n");

 
  for(;;)
  {
   
if (rc2014_pollc())
    {
      chr
= rc2014_getc();
      rc2014_putc
(chr);
   
}
  }
}

void rc2014_Print( const uint8_t * str)
{
  int16_t i
= 0;
  size_t stringlength
;

  stringlength
= strlen((char *)str);

  while(i < stringlength)
     rc2014_putc
( str[i++] );
}

A A

unread,
Oct 1, 2016, 3:18:01 AM10/1/16
to RC2014-Z80


On Friday, September 30, 2016 at 8:30:43 AM UTC-6, phillip.stevens wrote:


Ah, after spending a few hours trying to understand how the "correct" method for building the right low level drivers for the stdio library, I'm still at a complete loss. I would guess that the right way is to move the things I've just written into a library somewhere, and call them by the correct names (with public definitions). But beyond that it seems very confusing for me. Sorry for being thick.


I had way too much trouble with guthub tonight so instead I'm just going to zip up the target/rc2014 directory:
 https://drive.google.com/file/d/0B6XhJJ33xpOWdy1OSDhSZlZPYlE/view?usp=sharing

A list of changes:

1. All rc2014_* code has been moved to a new device/acia subdirectory.  I changed the rc2014_ prefix to acia_ in anticipation of code written for other devices.

2. There were a few PUBLICs missing from clib_target_constants.inc

3. I added some ascii char defines to clib_target_cfg.asm

4. clib_target_variables.inc has had all the acia data removed and moved to device/acia where it will be the linker's responsibility to add to the binary.

5. startup/rc2014_crt_0.m4 and the corresponding *.asm file has stdin, stdout, stderr added.  printf and scanf should now work using the acia drivers.  Maybe :)  In other targets there is usually a crt that can be chosen without any files instantiated to reduce memory footprint.

6. library/* all library list files are updated to include the new code in the driver and device subdirs.

7. The input and output device drivers are in driver/character.  The drivers themselves are the *.asm files and they are quite simple, just calling into your code to put or get a char.  The handling of the majority of stdio's messages is carried out by the library code.  The associated *.m4 files are instantiated in the crt.  The m4 macro reserves necessary memory for the drivers (your code doesn't require any but the library requires FILE* and fd structures).

8. device/acia is your driver code.  There was a bug in the initialization (acia_init) where constants were loaded with "ld a,(n)" instead of "ld a,n".  I don't know if this was the main cause of the problems.  I separated out a flush_Rx because the library needs a way to flush the input and output channels separately.  The acia initialization is wedged into the crt differently.  It is placed into section "crt_code_init" which is a section sequenced just before main is called.  So anything placed there will run before main.  I do a little cheat to make sure acia_init is placed there when it is needed.  In every acia function, you will see a "defc NEED_ACIA_INIT = _acia_init" at the bottom.  This generates a reference to that function, guaranteeing the linker will pull it in. 

There are a couple of things that came up:  will there be more than one ACIA chip?  If so, will there be one driver written for each of them with hard coded port addresses or will there be one driver shared amongst different ACIA instantiations?  If the latter, then the buffer code can be modified to use a buffer stored with the driver, that way giving each ACIA instantiation its own buffer.  Something like this probably needs more thought.

As interrupting devices are added, are they going to be added to im2 or will their isrs all be on im1?  If the latter, some scheme to chain isrs can be thought up.  The library does already have a "generic isr" that allows isrs to be registered with it.  On interrupt all isrs registered get a chance to run.  But it doesn't have to be done this way.

Anyway feel free to change what you don't like!  This code should allow printf and scanf both to function.  If they work, you can try to compile some of the examples in libsrc/_DEVELOPMENT/examples.  Many of those programs are fairly big so won't fit into a small ROM but if you can compile to RAM, they should be runnable.

phillip.stevens

unread,
Oct 1, 2016, 8:45:02 AM10/1/16
to RC2014-Z80
Thanks. Much appreciated.
AFK. Will follow up soonest.

phillip.stevens

unread,
Oct 2, 2016, 7:54:10 AM10/2/16
to RC2014-Z80

Ah, after spending a few hours trying to understand how the "correct" method for building the right low level drivers for the stdio library, I'm still at a complete loss. I would guess that the right way is to move the things I've just written into a library somewhere, and call them by the correct names (with public definitions). But beyond that it seems very confusing for me. Sorry for being thick.


I had way too much trouble with guthub tonight so instead I'm just going to zip up the target/rc2014 directory:
 https://drive.google.com/file/d/0B6XhJJ33xpOWdy1OSDhSZlZPYlE/view?usp=sharing

A list of changes:
 
A huge list of changes... 

I could have employed 1,000,000 monkeys for 10 years, and never come up with that result. I know I certainly never would have gotten to this point. Thanks, again.
I'm still not sure I understand the connection to the stdio platform. I'll have to digest this over the coming week.


8. device/acia is your driver code.  There was a bug in the initialization (acia_init) where constants were loaded with "ld a,(n)" instead of "ld a,n".  I don't know if this was the main cause of the problems.

Yes now you point it out. Being new to ASM, and I'm too loose with () as they are a thinking frame for me in C.
I need to be more sensitive that they imply indirection in z80 ASM.
It was one cause of problems, but there are still more to resolve.
 
I separated out a flush_Rx because the library needs a way to flush the input and output channels separately.

I removed the di ei and the push pops from the general acia_reset function, because it was done in the Rx and Tx flush functions.
 
The acia initialization is wedged into the crt differently.  It is placed into section "crt_code_init" which is a section sequenced just before main is called.  So anything placed there will run before main.  I do a little cheat to make sure acia_init is placed there when it is needed.  In every acia function, you will see a "defc NEED_ACIA_INIT = _acia_init" at the bottom.  This generates a reference to that function, guaranteeing the linker will pull it in.

My assumption is that this acia_init is now "mandatory" code and is run automatically at startup.
So calling it again would result in a hang, because there is no ret at its end?
 
There are a couple of things that came up:  will there be more than one ACIA chip?  If so, will there be one driver written for each of them with hard coded port addresses or will there be one driver shared amongst different ACIA instantiations?  If the latter, then the buffer code can be modified to use a buffer stored with the driver, that way giving each ACIA instantiation its own buffer.  Something like this probably needs more thought.

I'm assuming that Scott will want to do more things with the RC2014. Currently there is only one ACIA board available, so it makes sense to have the code written for one. I'm interested to know how to write for more I/O, as I'm building something with a z8s180 which has to integrated USARTs, but that's off topic here. 

As interrupting devices are added, are they going to be added to im2 or will their isrs all be on im1?  If the latter, some scheme to chain isrs can be thought up.  The library does already have a "generic isr" that allows isrs to be registered with it.  On interrupt all isrs registered get a chance to run.  But it doesn't have to be done this way.

The RC2014 only has one interrupt device, being the ACIA. There may be others that other users build at some stage in the future.

Anyway feel free to change what you don't like!  This code should allow printf and scanf both to function.  If they work, you can try to compile some of the examples in libsrc/_DEVELOPMENT/examples.  Many of those programs are fairly big so won't fit into a small ROM but if you can compile to RAM, they should be runnable.

I'm wondering if the printf() in its standard form goes to the right place, ACIA Tx. Or, is there something that needs to be set to configure this?

In Summary, I'm getting incorrect (unprintable) characters out of the emulator machine, but Tx is not working stand alone using the interrupt.
Again, not sure if the emulator or just wrong code.

Will keep on this over the week.

 

A A

unread,
Oct 2, 2016, 6:31:34 PM10/2/16
to RC2014-Z80


On Sunday, October 2, 2016 at 5:54:10 AM UTC-6, phillip.stevens wrote:

I could have employed 1,000,000 monkeys for 10 years, and never come up with that result. I know I certainly never would have gotten to this point. Thanks, again.
I'm still not sure I understand the connection to the stdio platform. I'll have to digest this over the coming week.

I'll try to get github working for me again and see if I can contribute that way.  I'm new to github so all the problems I had are all generated by yours truly.

If you're not thoroughly familiar with z80 yet, that will certainly add to the fog.  If you keep in mind that the code is object oriented, it will help to understand.  The first entry point is the driver in driver/character/rc_00_output_acia and it's being delivered one of the stdio messages (scroll down a bit http://www.z88dk.org/wiki/doku.php?id=temp:front#device_drivers ) with message type in "A" and other registers holding parameters.  You can satisfy those messages yourself or you can get the library to do the work.  In this case, the library is doing most of the work so instead of doing things, the messages are forwarded to the library base class (character_00_output).  The library base class itself will generate the one non-stdio message "OCHAR_MSG_PUTC" that the acia driver must implement, fundamentally a putc function.  While this goes on, IX is pointing at a fdstruct, which is a data structure with a jp instruction at the top and driver data at the bottom.  So a "jp (ix)" would jump to the driver and a "call jpix" is a way to send a message to the driver.  This is how the library base class sends the "OCHAR_MSG_PUTC" to the driver.  Message passing is how object orientation was originally implemented in smalltalk and is revisited here to enable function inheritance and overriding.  The IX data structure is structured to allow inheritance of data.
 
My assumption is that this acia_init is now "mandatory" code and is run automatically at startup.
So calling it again would result in a hang, because there is no ret at its end?

Yes maybe it is better to have it as a subroutine so it can be called normally.  Then you'd want to add "call _acia_init" to code_crt_init and that can still be done with the defc NEED thing but referencing another independent file containing just "SECTION code_crt_init  /  call _acia_init"

I probably screwed up the rst38h thing as well.... Your interrupt routine as two names, one regular name and one special _z80_rst38h used by the library.  The idea was if the user supplied his own rst38h, he could do so and still call your int routine by the other name.  But what will really happen is two "_z80_rst38h"s will be in global scope and cause a linking error.  So the right way to do this is create the default rst38h in a different way by having a "defc _z80_rst38h = _acia_im1" (I forgot the name here, just eg) in its own file.

One thing I've been wondering about is what is causing the interrupts?  Is it a timer or is it the ACIA on reception of a char?  If the latter, then can chars be output independently of receiving any?

I'm assuming that Scott will want to do more things with the RC2014. Currently there is only one ACIA board available, so it makes sense to have the code written for one. I'm interested to know how to write for more I/O, as I'm building something with a z8s180 which has to integrated USARTs, but that's off topic here.

We're not quite sure what the right way to do things is yet.  Ideally you'd want to be able to say "I've got two ACIAs, here are the port mappings" and then you should be able to instantiate software for those devices somehow.  The rc2014 is a practical way to sort this out since it's got a variable plug-in architecture.

I'm wondering if the printf() in its standard form goes to the right place, ACIA Tx. Or, is there something that needs to be set to configure this?

The assignment of FILE* and file descriptors happens in the crt macro (target/rc2014/startup/rc2014_crt_0.m4), as of now:

include(../../clib_instantiate_begin.m4)

include(../driver/character/rc_00_input_acia.m4)
m4_rc_00_input_acia(_stdin, 0x0100)

include(../driver/character/rc_00_output_acia.m4)
m4_rc_00_output_acia(_stdout, 0x0100)

include(../../m4_file_dup.m4)
m4_file_dup(_stderr, 0x80, __i_fcntl_fdstruct_1)

include(../../clib_instantiate_end.m4)

The file descriptors are in order so the first instantiation (m4_rc_00_input_acia) is assigned to fd=0, the second (m4_rc_00_output_acia) to fd=1 and so on.  The file descriptor table is statically built by adding a pointer to the driver in the fd table for each macro.

The FILE* are created by name.  stdout is attached to m4_rc_00_output_acia so printf will go to that driver.  By virtue of the dup, fprintf(stderr,...) will also go to that driver.

There probably is not an issue with the connection to printf.  Does Scott's emulator support breakpoints?  The label "rc_00_output_acia_ochar_msg_putc" in target/rc2014/driver/character/rc_00_output_acia.asm is where the driver is trying to send a char to your ACIA Tx code.  If you compile a test program with "-m" to get the map file, you can look up the address of "rc_00_output_acia_ochar_msg_putc" and stick a breakpoint there to see what chars the driver is trying to print.  The other thing with this driver is that it is blocking -- if the Tx buffer is full, it will spin and wait until an opening is available to write the next char before returing.  The interrupt code must be emptying the Tx buffer to allow the driver to return when the buffer is full.

Will keep on this over the week.

I will take another look tonight to see if I can spot any errors.
 

A A

unread,
Oct 2, 2016, 6:39:42 PM10/2/16
to RC2014-Z80


On Sunday, October 2, 2016 at 4:31:34 PM UTC-6, A A wrote:

The FILE* are created by name.  stdout is attached to m4_rc_00_output_acia so printf will go to that driver.  By virtue of the dup, fprintf(stderr,...) will also go to that driver.

And then to complete the thought, the macro will contain a jump to the driver's address.  m4_rc_00_output_acia.m4 contains this:

      ; jump to driver
     
      defb 195
      defw rc_00_output_acia

That's the part I could screw up but it is the Tx driver.

 

A A

unread,
Oct 3, 2016, 12:46:09 AM10/3/16
to RC2014-Z80

I don't have permission to send a pull request so I've zipped up the rc2014 directory again:
https://drive.google.com/open?id=0B6XhJJ33xpOWdy1OSDhSZlZPYlE

Some of the changes:

* Change of names from "__acia_XX_data" to "__acia_bss_XX" so that
data/bss variables are separated and sortable according to section 
* Renamed the two flush functions to "*flush_XX_di" and "*flush_XX" so
that it is clear which version disables interrupts and which doesn't
* The acia_init function is now a callable function with return address.
The code_crt_init insert is now a call to this function.
* The acia's im1 routine is now called _acia_interrupt with no
z80_rst_38h alias.  Instead the default selection of im1 routine is made
in a separate z80_rst_38h.asm file.  This is to avoid a linking error
where user declares own z80_rst_38h and calls acia_interrupt directly;
this would bring two z80_rst_38h into global scope.
* Temporary fix to bug associated with declaring the aciaTxBuffer and
the aciaRxBuffer.  At library build time their sizes "ACIA_TX_SIZE" and
"ACIA_RX_SIZE" were unavailable so the library was creating zero-size
buffers.  The temporary fix is hardcoding the sizes but this should be
fixed up when a better way to instantiate devices is introduced.

With these changes I can see "Hello World" written to the Tx Buffer under emulation (not an rc2014 emulation since I have to figure out how to build under windows and I haven't done that).

// zcc +rc2014 -vn -subtype=rom test.c -o test -create-app -m --list --c-code-in-asm

#pragma output CLIB_OPT_PRINTF = 0     // disable all % converters

#include <stdio.h>

void main(void)
{
   printf("Hello World\n");
}

The main bug was in the declaration of the Tx and Rx buffers.  When they are compiled into the library, their sizes must be known, but they were not since the size defines are in a compile-time definitions file.  This led to Tx and Rx buffers of size 0 in the program.  I made a temporary change where the sizes are hard coded to $80 bytes as defined by the compile-time constants.

I have some ideas about how to instantiation devices so maybe a proper solution can be tried when that's hacked out in the next day or two.

Phillip Stevens

unread,
Oct 3, 2016, 1:38:29 AM10/3/16
to rc201...@googlegroups.com
Did you clone the repo? If so I can add you to enable further pulls. There was only one clone to date. 
--
You received this message because you are subscribed to a topic in the Google Groups "RC2014-Z80" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/rc2014-z80/-4jOOxYXGEo/unsubscribe.
To unsubscribe from this group and all its topics, send an email to rc2014-z80+unsubscribe@googlegroups.com.

To post to this group, send email to rc201...@googlegroups.com.
To view this discussion on the web, visit https://groups.google.com/d/msgid/rc2014-z80/d1bddd70-e570-4103-8858-1d3dac0a6bfd%40googlegroups.com.

For more options, visit https://groups.google.com/d/optout.


--
Sent from a Mobile Device.
Replies may appear terse.

A A

unread,
Oct 3, 2016, 2:14:29 AM10/3/16
to RC2014-Z80


On Sunday, October 2, 2016 at 11:38:29 PM UTC-6, phillip.stevens wrote:
Did you clone the repo? If so I can add you to enable further pulls. There was only one clone to date. 


I think I did.  This is the first time I've used guthub so I think I cloned it, think I created a branch and I think created a pull request.  But the pull request is saying I don't have access.  If you're on and can give pull permission I can send the changes that way before heading to bed.
 

Filippo Bergamasco

unread,
Oct 3, 2016, 3:02:28 AM10/3/16
to rc201...@googlegroups.com
To create a pull request please ensure that:

1) the changes you are proposing are on a branch different than "master". This is in general a good idea when introducing a new feature The same should apply for the repo in which your changes are going to be applied (but I think it's not mandatory).

2) The upstream branch is referring the correct repository in which your changes should be applied.

Then, you should simply follow these steps:


Phillip, just tell me when you want your changes being referenced (or included) in the official RC2014 repository.

Filippo



--
You received this message because you are subscribed to the Google Groups "RC2014-Z80" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rc2014-z80+unsubscribe@googlegroups.com.

To post to this group, send email to rc201...@googlegroups.com.

A A

unread,
Oct 3, 2016, 3:25:48 AM10/3/16
to RC2014-Z80


On Monday, October 3, 2016 at 1:02:28 AM UTC-6, Filippo Bergamasco wrote:

I'll probably have to have a look at this again tomorrow.  I'm using github desktop to keep it easy (!) and following these instructions:

https://help.github.com/desktop/guides/contributing/committing-and-reviewing-changes-to-your-project/
https://help.github.com/desktop/guides/contributing/sending-a-pull-request/

I've created a separate branch but I cannot sync with the remote repository.  It says I don't have permission to push changes to the repository.  It sounds like there is some configuration that needs to be done at Philip's end but since this is new for me I can't say for sure.

 

phillip.stevens

unread,
Oct 3, 2016, 7:42:28 AM10/3/16
to RC2014-Z80

I don't have permission to send a pull request so I've zipped up the rc2014 directory again:
https://drive.google.com/open?id=0B6XhJJ33xpOWdy1OSDhSZlZPYlE

I have added you to the repo as a collaborator, so you can commit directly now.
I' pushed the latest changes to the repo, so I think that is close to final.
But some questions below.
 
Some of the changes:

* Change of names from "__acia_XX_data" to "__acia_bss_XX" so that
data/bss variables are separated and sortable according to section 
* Renamed the two flush functions to "*flush_XX_di" and "*flush_XX" so
that it is clear which version disables interrupts and which doesn't
* The acia_init function is now a callable function with return address.
The code_crt_init insert is now a call to this function.
* The acia's im1 routine is now called _acia_interrupt with no
z80_rst_38h alias.  Instead the default selection of im1 routine is made
in a separate z80_rst_38h.asm file.  This is to avoid a linking error
where user declares own z80_rst_38h and calls acia_interrupt directly;
this would bring two z80_rst_38h into global scope.
* Temporary fix to bug associated with declaring the aciaTxBuffer and
the aciaRxBuffer.  At library build time their sizes "ACIA_TX_SIZE" and
"ACIA_RX_SIZE" were unavailable so the library was creating zero-size
buffers.  The temporary fix is hardcoding the sizes but this should be
fixed up when a better way to instantiate devices is introduced.

With these changes I can see "Hello World" written to the Tx Buffer under emulation (not an rc2014 emulation since I have to figure out how to build under windows and I haven't done that).

// zcc +rc2014 -vn -subtype=rom test.c -o test -create-app -m --list --c-code-in-asm

#pragma output CLIB_OPT_PRINTF = 0     // disable all % converters

#include <stdio.h>

void main(void)
{
   printf("Hello World\n");
}

The main bug was in the declaration of the Tx and Rx buffers.  When they are compiled into the library, their sizes must be known, but they were not since the size defines are in a compile-time definitions file.  This led to Tx and Rx buffers of size 0 in the program.  I made a temporary change where the sizes are hard coded to $80 bytes as defined by the compile-time constants.

I was guessing that it was a buffer declaration issue.
I couldn't see the buffers in the .map file, but I imagined it was the nature of the library mechanism.

Good news! We are close, very close!


On a real rc2014, that is the output. The code uses both the stdio library, and my hacked print, just to test the simple routines.
Both work well, and the Tx interrupt is doing its job.

On the z80-machine emulator, Tx only works when Rx is being received. Two characters Tx out, for each one Rx in.
I've attached the test.ihx, in case anyone else wants to see the effect.

But, there is still an error.  On the real rc2014, the Rx characters are not echoed out (using below code). But, on the z80-machine emulator, it works fine.
These two things together are odd. I'm not sure why the emulator would work (for Rx), where the real device wouldn't?

#include <stdio.h>
#include <string.h>

extern void acia_init(void);            // initialise the ACIA, and Interrupt
extern void acia_reset(void);           // initialise the Tx & Rx buffers
extern void acia_flush_Rx(void);        // initialise the Rx buffer
extern void acia_flush_Tx(void);        // initialise the Tx buffer
extern uint8_t acia_pollc(void);        // Rx polling routine, checks Rx buffer fullness
extern uint8_t acia_getc(void);         // Rx receive routine, from Rx buffer
extern uint8_t acia_peekc(void);        // Rx peek routine, reads Rx without removing it from buffer
extern uint8_t acia_putc(uint8_t) __z88dk_fastcall; // Tx write routine, writes to Tx buffer

void acia_print( uint8_t const * str);

void main(void)
{
    uint8_t chr
;

    acia_print
("Hello World\r\n");
    printf
("Hello World - Again\r\n");

   
for(;;)
   
{
       
if (acia_pollc())
       
{
            chr
= acia_getc();
            acia_putc
(chr);
       
}
   
}
}

void acia_print( const uint8_t * str)

{
    int16_t i
= 0;
    size_t stringlength
;

    stringlength
= strlen((char *)str);

   
while(i < stringlength)

        acia_putc
( str[i++]);
}


I have some ideas about how to instantiation devices so maybe a proper solution can be tried when that's hacked out in the next day or two.


One of the data fields I added early was an exclusion flag for the Tx. I don't know whether the stdio library automatically prevents multiple Tx threads from overwriting each other, but I find it a cheap way to prevent garbled messages when using freeRTOS (cheaper than a RTOS Semaphore, anyway). Should I add code to manage mutual exclusion on Tx? Or does that come for free with z88dk stdio?

And my ROM is officially full of dead code. Off to Digikey to get some more.

test.ihx

Phillip Stevens

unread,
Oct 3, 2016, 7:54:27 AM10/3/16
to rc201...@googlegroups.com

___________________________________________
Phillip Stevens
GSM: +61 4 3322 0688
mailto:phillip...@GMail.com
Key: E0E09601
___________________________________________

--
You received this message because you are subscribed to the Google Groups "RC2014-Z80" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rc2014-z80+unsubscribe@googlegroups.com.
To post to this group, send email to rc201...@googlegroups.com.

phillip.stevens

unread,
Oct 3, 2016, 8:44:35 PM10/3/16
to RC2014-Z80


On Monday, 3 October 2016 22:42:28 UTC+11, phillip.stevens wrote:

I don't have permission to send a pull request so I've zipped up the rc2014 directory again:
https://drive.google.com/open?id=0B6XhJJ33xpOWdy1OSDhSZlZPYlE

I have added you to the repo as a collaborator, so you can commit directly now.
I' pushed the latest changes to the repo, so I think that is close to final.
But some questions below.
 
Some of the changes:

Good news! We are close, very close!


Just looking at the code, I'm concerned by the acia_init routine. It doesn't preserve the af registers, yet it uses them. Also, it calls the acia_reset routine, that doesn't preserve af or hl, yet it in turn calls the versions of flush_rx flush_tx that don't preserve registers (this was moved out of the non interrupt protected versions of the code). Could this be an issue?

It looks like my "raw" drivers have also been adapted to no register preservation. I guess that means that they will also break things if called directly.
I think that's probably the cause of my "echo" routine not working.
That's no problem, to use the standard stdio way of calling for getting and writing characters, provided that I don't make the device driver public.

Another question that I'd pose is how the interface between the "device" and the "driver" works.
I would like to write some multi character buffer based puts(), and gets() kind of drivers.
But if stdio can't use these interfaces, then it won't be worth doing. What do you think?
 
One of the data fields I added early was an exclusion flag for the Tx. I don't know whether the stdio library automatically prevents multiple Tx threads from overwriting each other, but I find it a cheap way to prevent garbled messages when using freeRTOS (cheaper than a RTOS Semaphore, anyway). Should I add code to manage mutual exclusion on Tx? Or does that come for free with z88dk stdio?


It looks like there is a spinlock definition in the m4 character driver.  defb 0xfe ; atomic spinlock Could this be the way to provide mutual exclusion for multithreaded applications?

Cheers, Phillip


A A

unread,
Oct 3, 2016, 10:48:34 PM10/3/16
to RC2014-Z80


On Monday, October 3, 2016 at 6:44:35 PM UTC-6, phillip.stevens wrote:

Just looking at the code, I'm concerned by the acia_init routine. It doesn't preserve the af registers, yet it uses them. Also, it calls the acia_reset routine, that doesn't preserve af or hl, yet it in turn calls the versions of flush_rx flush_tx that don't preserve registers (this was moved out of the non interrupt protected versions of the code). Could this be an issue?


No, it's not an issue.  The C compiler does not expect any registers to be preserved except ix and (iy when --reserve-regs-iy is not present).  An sdcc iy compile adds that flag automatically.  However, for assembly language subroutines you can tell the compiler that certain registers are not modified with the preserves_regs attribute:

extern uint8_t acia_putc(uint8_t) __preserves_regs(b,c,d,e,iyl,iyh) __z88dk_fastcall; // Tx write routine, writes to Tx buffer
(try to compare the asm output with that preserve regs attribute there and not; most often the compiler can make best use of bc and de being unchanged)

So there is no issue with C calling asm functions; the only requirement is that the asm implements the promised parameter linkage and returns values properly in a subset of DEHL.

As for the asm interface, documentation on what registers are modified is enough.  Anyone writing asm can read the source code and see what's what.  Sometimes saving some registers can be helpful to the caller but most often I don't save.  Some use cases of the called subroutine can help sort that out.
 
It looks like my "raw" drivers have also been adapted to no register preservation. I guess that means that they will also break things if called directly.
I think that's probably the cause of my "echo" routine not working.

They should still work fine from C.

Another question that I'd pose is how the interface between the "device" and the "driver" works.
I would like to write some multi character buffer based puts(), and gets() kind of drivers.
But if stdio can't use these interfaces, then it won't be worth doing. What do you think?

The sole interface from stdio to the device is through the driver using the small set of messages defined at ( http://www.z88dk.org/wiki/doku.php?id=temp:front#device_drivers ).  You can think of those messages as function identifiers (not really function pointers since addresses are not being sent).

stdio does try to send entire buffers and you can implement buffer writes to the device by implementing the stdio messages directly rather than passing the responsibility to the library driver.

puts() is going to generate the stdio message A=STDIO_MSG_WRIT with registers HL'=void *src, HL=BC'=len and send that to the driver.  Right now the acia output driver does this:

rc_00_output_acia:

   cp OCHAR_MSG_PUTC
   jp nz, character_00_output  ; forward other messages to library

rc_00_output_acia_ochar_msg_putc:

   ;   enter   :  c = char
   ;   exit    : carry set if error
   ;   can use : af, bc, de, hl, af'
  
block_loop:

   ld l,c
   call _acia_putc
  
   dec l
   jr z, block_loop            ; if Tx buffer is full
  
   ret                         ; carry is reset


This driver does not deal with that message, instead it forwards to the library which will break it up into individual OCHAR_MSG_PUTC, one message per char.  This way the driver can be as simple as a putc which is what we have done.

However, you can instead service the STDIO_MSG_WRIT message directly in this driver and send the entire string in one go.  The driver will be a little bit faster as a result.  It is up to you if you want to do that.

I also always make sure that the device is accessible outside of stdio, for example the low level acia_* routines you've written.  Although the z88dk stdio implementation is about as light as it gets, the memory space is still tight, so people will sometimes prefer or even need to use the low level interface to gain in memory.  If you think a low level puts and gets would work well in the absence of stdio, I'd add it.  No one has to use it and if they don't use it, it doesn't cost anything.

One of the data fields I added early was an exclusion flag for the Tx. I don't know whether the stdio library automatically prevents multiple Tx threads from overwriting each other, but I find it a cheap way to prevent garbled messages when using freeRTOS (cheaper than a RTOS Semaphore, anyway). Should I add code to manage mutual exclusion on Tx? Or does that come for free with z88dk stdio?


It looks like there is a spinlock definition in the m4 character driver.  defb 0xfe ; atomic spinlock Could this be the way to provide mutual exclusion for multithreaded applications? 

stdio is preparing for multi-threading but it's not ready yet.   The C11 standard has specified a number of multi-threading related functions and the z88dk c lib has mutexes and spinlocks tentatively implemented (see the libsrc/_DEVELOPMENT/threads directory) to implement those functions but since there is no thread scheduling code in the library there is nothing to do if a thread is supposed to block.

FILE* and file descriptors each have mutexes associated with them.  stdio is going to the trouble of acquiring those mutexes and there are functions flockfile, funlockfile, ftrylockfile that will lock a FILE* and others (not sure if they are there yet) that will lock file descriptors.  However, again if a thread is supposed to block, it won't because there is no scheduler.  I suppose you might be able to do FILE* locking on the honour system by using ftrylockfile() and funlockfile().

In other words, no, if you want mutual exclusion you will have to do it for the time being.  Inside the output driver,you can return success or error on output.  The non-blocking drivers are not done so the only choice on blocking is to spin or return error.  If you return an error, the calling program has to clear the error before it can continue to use the FILE* successfully, see clearerr()

Maybe I should spend some time on that end of things if you are considering multi-threading and non-blocking i/o.  Or if you'd like to contribute that's ok too :)

A A

unread,
Oct 3, 2016, 11:16:07 PM10/3/16
to RC2014-Z80


On Monday, October 3, 2016 at 6:44:35 PM UTC-6, phillip.stevens wrote
Just looking at the code, I'm concerned by the acia_init routine. It doesn't preserve the af registers, yet it uses them. Also, it calls the acia_reset routine, that doesn't preserve af or hl, yet it in turn calls the versions of flush_rx flush_tx that don't preserve registers (this was moved out of the non interrupt protected versions of the code). Could this be an issue?

For acia_init maybe some documentation on what registers are modified would be helpful.  It's going to be called just before main as a call is inserted into section code_crt_init but at that point, no registers need to be preserved.  And as mentioned before regarding the C compiler interface, the C compiler is not expecting any registers except ix and iy to be preserved.

But maybe acia_reset should go back to disabling interrupts if it's going to be a low-level exposed function.  Then acia_init can call the Tx and Rx flushing functions that don't disturb the interrupt disable.

Phillip Stevens

unread,
Oct 3, 2016, 11:38:42 PM10/3/16
to rc201...@googlegroups.com
extern uint8_t acia_putc(uint8_t) __preserves_regs(b,c,d,e,iyl,iyh) __z88dk_fastcall; // Tx write routine, writes to Tx buffer
(try to compare the asm output with that preserve regs attribute there and not; most often the compiler can make best use of bc and de being unchanged)

Interesting. I'll try this, and look at the diffs.


So there is no issue with C calling asm functions; the only requirement is that the asm implements the promised parameter linkage and returns values properly in a subset of DEHL.

It looks like my "raw" drivers have also been adapted to no register preservation. I guess that means that they will also break things if called directly.
I think that's probably the cause of my "echo" routine not working.

They should still work fine from C.

Oh? That's disappointing. ;-( 

I thought I'd found the issue why the test didn't work... back to bug hunt.
 

Another question that I'd pose is how the interface between the "device" and the "driver" works.
I would like to write some multi character buffer based puts(), and gets() kind of drivers.
But if stdio can't use these interfaces, then it won't be worth doing. What do you think?

The sole interface from stdio to the device is through the driver using the small set of messages defined at ( http://www.z88dk.org/wiki/doku.php?id=temp:front#device_drivers ).  You can think of those messages as function identifiers (not really function pointers since addresses are not being sent).

stdio does try to send entire buffers and you can implement buffer writes to the device by implementing the stdio messages directly rather than passing the responsibility to the library driver.

However, you can instead service the STDIO_MSG_WRIT message directly in this driver and send the entire string in one go.  The driver will be a little bit faster as a result.  It is up to you if you want to do that.

Ok. I now understand a little better what the stdio library is expecting. So I'll have a go at the appropriate puts() and gets() functions for stdio. 


I also always make sure that the device is accessible outside of stdio, for example the low level acia_* routines you've written.  Although the z88dk stdio implementation is about as light as it gets, the memory space is still tight, so people will sometimes prefer or even need to use the low level interface to gain in memory.  If you think a low level puts and gets would work well in the absence of stdio, I'd add it.  No one has to use it and if they don't use it, it doesn't cost anything.

I was thinking the other way around. Not the need to expose puts() and  gets(), but rather could stdio use these functions to be faster?
I think you've answered yes to that question. So yes, I'll have a go.
 

One of the data fields I added early was an exclusion flag for the Tx. I don't know whether the stdio library automatically prevents multiple Tx threads from overwriting each other, but I find it a cheap way to prevent garbled messages when using freeRTOS (cheaper than a RTOS Semaphore, anyway). Should I add code to manage mutual exclusion on Tx? Or does that come for free with z88dk stdio?

stdio is preparing for multi-threading but it's not ready yet.   The C11 standard has specified a number of multi-threading related functions and the z88dk c lib has mutexes and spinlocks tentatively implemented (see the libsrc/_DEVELOPMENT/threads directory) to implement those functions but since there is no thread scheduling code in the library there is nothing to do if a thread is supposed to block.
 
I will be implementing freeRTOS for my z8s180 project, where the scheduler can be driven by an integrated timer. But, on pure z80 it is difficult to establish a preemptive scheduler, because there is no discrete timer. Perhaps it might be worth using freeRTOS co-routines (cooperative scheduling) in this situation, because the Task Control Block structures would remain constant between preemptive and cooperative versions of the outcome? Keeping TCB design similar may be a useful outcome.


Maybe I should spend some time on that end of things if you are considering multi-threading and non-blocking i/o.  Or if you'd like to contribute that's ok too :)

I have a minimal AVR freeRTOS repo, which might be useful to look at the way that TCBs etc are implemented in freeRTOS. I think it would be pretty simple to implement this in C for z88dk and then performance profile it to see which parts need to be moved to ASM over time.


For the record, the Event Groups (Flags) and MPU Wrappers can be left out with no detriment. Similarly, timers.c is for software timers, and can be deleted too. There's really only list.c, task.c, and queues.c that are the core system, with coroutine.c providing the cooperative variant for scheduling.

Here is another repo with an eZ80 freeRTOS port ready to go. I would be using this as a guide, and working back towards z80 support.


Doing this freeRTOS work is on my road-map, but comes after getting this stdio stuff working, and then building some new hardware with a timer (hardware testing, CUPL debugging, crt0 config, etc) before I can get back to multitasking on z80... 

A A

unread,
Oct 4, 2016, 2:25:50 AM10/4/16
to RC2014-Z80


On Monday, October 3, 2016 at 9:38:42 PM UTC-6, phillip.stevens wrote:

However, you can instead service the STDIO_MSG_WRIT message directly in this driver and send the entire string in one go.  The driver will be a little bit faster as a result.  It is up to you if you want to do that.

Ok. I now understand a little better what the stdio library is expecting. So I'll have a go at the appropriate puts() and gets() functions for stdio.
I was thinking the other way around. Not the need to expose puts() and  gets(), but rather could stdio use these functions to be faster?
I think you've answered yes to that question. So yes, I'll have a go.

This rather important point always slips my mind.. but we do have to at least consider text versus binary mode transmission.  The library driver code that is being used now is a text mode driver.  What this means is it will automatically do CRLF conversion.  So if you print "hello world\n" it will change the string to "hello world\r\n" when the string is sent to the device (it's also smart enough to properly send \r\n even if the C string also contains \r\n).  Likewise, on receiving characters, the library will change all combinations of \r\n from the device to a single \n.  So the C code only ever deals with a single \n terminator.  The CRLF behaviour can be shut off either at runtime via ioctl() or at instantiation time in the crt with appropriate flags.  If you switch these things off, the C library will treat \r like a literal character of no special significance.  However, it may show up as unexpected to programmers.  Using getline() for example, will return a string terminated in \r if the CRLF is shut off and the device does \r\n line termination.  If the device does not do \r\n line termination, the string returned will not have a \r at the end.  This difference can sometimes be a problem for programmers.

The reason why I bring this up is because a text mode driver has to examine each character received from the device whereas a binary mode driver does not.  The STDIO_MSG_WRIT message initializes registers such that the driver could use z80 block instructions to output the buffer.  However you can't use those block instructions in a text mode driver.

Anyway, this is relevant if you are implementing the STDIO_MSG_WRIT message directly because if this is a text mode driver, you'll have to output chars individually and do CRLF conversion.  Inside the output driver you can test bit 4 of ix+6 for non-zero to see if CRLF mode is on.

You can see how the text mode STDIO_MSG_WRIT function is implemented by the library's character driver in this directory:
libsrc/_DEVELOPMENT/drivers/character/character_00/output/character_00_output

What it's doing is it reads the next char to output and sends it to the driver as a OCHAR_MSG_PUTC_BIN message.  If the driver is a binary driver it would catch that message and output the char.  However if the driver is a text mode driver, like the acia output driver now, that message comes back to the library and you can see that implementation in the same directory.  This time the library checks if CRLF mode is on and if so does the CRLF conversion before forwarding the char this time as a OCHAR_MSG_PUTC message.  This is the message the acia output driver intercepts so it gets the char after the library has already done the conversion.

When I ran the printf helloworld program under emulation, the emulator showed about 450 cycles per char (hello world will be printed using STDIO_MSG_WRIT).  That's all this bouncing around + the time it took for your code to place the char into the Txbuffer.




 

A A

unread,
Oct 4, 2016, 2:37:57 AM10/4/16
to RC2014-Z80


On Monday, October 3, 2016 at 9:38:42 PM UTC-6, phillip.stevens wrote:


Maybe I should spend some time on that end of things if you are considering multi-threading and non-blocking i/o.  Or if you'd like to contribute that's ok too :)

I have a minimal AVR freeRTOS repo, which might be useful to look at the way that TCBs etc are implemented in freeRTOS. I think it would be pretty simple to implement this in C for z88dk and then performance profile it to see which parts need to be moved to ASM over time.



I'll definitely have a look.  I don't know if FreeRTOS accommodates it, but the TCB may have to contain variable length thread local storage.  C11 is defining tls and in addition to that we might put some library elements into tls.  For example, we might make the heap a thread property so that each thread has its own heap to work with and we can bypass blocking issues on malloc.  We can easily create a tls section and have the linker put whatever into it so that the running thread will have a contiguous block where its TCB+tls is located in memory.  Then at context switch, that memory area is saved and the next thread's data is written there.
 

Phillip Stevens

unread,
Oct 4, 2016, 3:07:48 AM10/4/16
to rc201...@googlegroups.com
I think this was added in freeRTOS in 8.2.1... so there is an option for that, designed for pointers, but works for integers too.


I was thinking of putting the CBR / BBR in there and enabling bank switching for each task using the z8s180. Then the heap size / task number equation could be pretty flexible.

phillip.stevens

unread,
Oct 4, 2016, 7:32:22 AM10/4/16
to RC2014-Z80
It looks like my "raw" drivers have also been adapted to no register preservation. I guess that means that they will also break things if called directly.
I think that's probably the cause of my "echo" routine not working.

They should still work fine from C.

Oh? That's disappointing. ;-( 

I thought I'd found the issue why the test didn't work... back to bug hunt.

Could someone try this test.hex file on a real rc2014, please?
It is implementing this C code below with the C stdio implementation from this repository.

#include <stdio.h>

void main(void)
{
    uint8_t chr
;
 
    printf
("Hello World\r\n");

   
while ((chr=getchar()) != EOF)   /* read/print "abcde" from stdin */
          printf
("%c", chr);
}

It is working on the z80-machine emulator, but the Tx interrupt is not behaving the same as on a real machine.
Any feedback welcome.

Thanks in advance.
test.hex

Phillip Stevens

unread,
Oct 4, 2016, 11:53:17 PM10/4/16
to rc201...@googlegroups.com
Phillip, just tell me when you want your changes being referenced (or included) in the official RC2014 repository.

Filippo, I need to hold on this for while whilst waiting for some new ROMs to arrive, but I think it is pretty close to being finished now.

When it is in a completely working state (in about 2 weeks or so), please clone the whole repository using the rc2014 user name  and change the name to z88dk-rc2014 (or similar). I will ensure that the latest z88dk and sdcc upstream code is integrated before you do this. Then an AMD64 Linux user just has to download the repo to get a pre-built C development environment.

At some stage then I hope the z88dk team will integrate the rc2014 target into their main-line code and when that happens it should be easy for everyone to just use that as the main source of truth. 

Filippo Bergamasco

unread,
Oct 6, 2016, 8:44:15 AM10/6/16
to rc201...@googlegroups.com
So, if I've understood correctly, you are suggesting to fork your z88dkrc2014 repo with the official rc2014 github account (https://github.com/RC2014Z80) so that you can continue the development from there?
I can give you full access to such repo if you want, so you can manage it by yourself

Filippo



--
You received this message because you are subscribed to the Google Groups "RC2014-Z80" group.
To unsubscribe from this group and stop receiving emails from it, send an email to rc2014-z80+unsubscribe@googlegroups.com.
To post to this group, send email to rc201...@googlegroups.com.

Phillip Stevens

unread,
Oct 6, 2016, 9:08:13 AM10/6/16
to rc201...@googlegroups.com
Filippo,

that works too. Then, I can do it myself. :-)
No one else to blame then.

Thanks, Phillip

___________________________________________
Phillip Stevens
GSM: +61 4 3322 0688
mailto:phillip...@GMail.com
Key: E0E09601
___________________________________________

You received this message because you are subscribed to a topic in the Google Groups "RC2014-Z80" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/rc2014-z80/-4jOOxYXGEo/unsubscribe.
To unsubscribe from this group and all its topics, send an email to rc2014-z80+unsubscribe@googlegroups.com.

To post to this group, send email to rc201...@googlegroups.com.

A A

unread,
Oct 8, 2016, 2:14:26 AM10/8/16
to RC2014-Z80


On Tuesday, October 4, 2016 at 5:32:22 AM UTC-6, phillip.stevens wrote:
Could someone try this test.hex file on a real rc2014, please?
It is implementing this C code below with the C stdio implementation from this repository.

#include <stdio.h>

void main(void)
{
    uint8_t chr
;
 
    printf
("Hello World\r\n");

   
while ((chr=getchar()) != EOF)   /* read/print "abcde" from stdin */
          printf
("%c", chr);
}

It is working on the z80-machine emulator, but the Tx interrupt is not behaving the same as on a real machine.
Any feedback welcome.


I can't test it but I can't see anything wrong with the code either, other than a concurrency issue with access to the control register (I've sent a pull request).  The only thing I haven't doublechecked is the acia initialization against the datasheet and the changes to the acia interrupts when the rx/tx buffers are filled/emptied.

For devices and their low-level code I'm looking at making templates out of them using m4.  This will make it possible to create multiple instances of devices mapped to different ports inside a target configuration file.  This means you could define a variable system by plugging things in like lego.

The templatization does mean all the low-level interface code will be duplicated for each device with different names.  I may try another method that doesn't duplicate the code later but I think the template method would be most often preferred since it will not slow down the code.

phillip.stevens

unread,
Nov 6, 2016, 7:20:53 AM11/6/16
to RC2014-Z80
Hard to believe that over a month has passed.
Postage from the UK and China definitely comes on the slow boat.

Any way, some good news. With some close examination of the code, and some hours of pondering, I found the simple problem stopping my interrupt routine from working properly. I just needed to disable the TX Interrupt at the appropriate point. Having done this one small change, the Tx and Rx routines now work properly.

This is a screen capture of "hello\r" being received and then echoed out the Tx line, using the interrupt driven IO routines.


extern uint8_t acia_pollc(void) __preserves_regs(b,c,d,e,iyl,iyh); // Rx polling routine, checks Rx buffer fullness
extern uint8_t acia_getc(void) __preserves_regs(b,c,d,e,iyl,iyh);  // Rx receive routine, from Rx buffer
extern uint8_t acia_peekc(void) __preserves_regs(b,c,d,e,iyl,iyh); // Rx peek routine, reads Rx without removing it from buffer
extern uint8_t acia_putc(uint8_t) __preserves_regs(b,c,d,e,iyl,iyh) __z88dk_fastcall; // Tx write routine, writes to Tx buffer

    for(;;)
   
{
       
if (acia_pollc() != 0)
       
{
            chr
= acia_getc();
            acia_putc
( chr);
       
}
   
}

But now the bad news. I still haven't been able to get the linkage to the STDIO to work properly.

I'm afraid that I'm a bit out of my depth. The intermediate routines look like they're right.
I did change the input routine to use _acia_pollc to avoid needless IO reads from the ACIA device. But I think its ok.

rc_00_input_acia:

   cp ICHAR_MSG_GETC
   jr nz
, no_match_0

rc_00_input_acia_ichar_msg_getc
:

   
;  exit : a = keyboard char after character set translation
   
;         carry set on error, hl = 0 (stream error) or -1 (eof)
   
;
   
;  can use : af, bc, de, hl

block_loop
:

   call _acia_pollc            
; check whether any characters are in Rx buffer
   jr nc
, block_loop           ; if Rx buffer is empty
   
   call _acia_getc
   
   
; l = ascii code
   
   ld a
,l
   
   cp CHAR_CTRL_Z
   jp z
, error_mc              ; generate EOF (ctrl-z is from cp/m)
   
   
or a                        ; reset carry to indicate no error
   ret

Otherwise, I'll have to go through level by level, checking with hardware.
The z80 machine doesn't replicate the Tx interrupt well, so I have to use hardware to test.

So that's it. Getting really closed to full z88dk support for rc2014.



On Saturday, 8 October 2016 17:14:26 UTC+11, A A wrote:

On Tuesday, October 4, 2016 at 5:32:22 AM UTC-6, phillip.stevens wrote:
Could someone try this test.hex file on a real rc2014, please?
It is implementing this C code below with the C stdio implementation from this repository.

#include <stdio.h>

void main(void)
{
    uint8_t chr
;
 
    printf
("Hello World\r\n");

   
while ((chr=getchar()) != EOF)   /* read/print "abcde" from stdin */
          printf
("%c", chr);
}

It is working on the z80-machine emulator, but the Tx interrupt is not behaving the same as on a real machine.
Any feedback welcome.
For devices and their low-level code I'm looking at making templates out of them using m4.  This will make it possible to create multiple instances of devices mapped to different ports inside a target configuration file.  This means you could define a variable system by plugging things in like lego.

Great idea. I will be able to copy this for the YAZ180 z8s180 board I'm making, as it has two integrated USART ports.

The templatization does mean all the low-level interface code will be duplicated for each device with different names.  I may try another method that doesn't duplicate the code later but I think the template method would be most often preferred since it will not slow down the code.

Agree. Templates are the best way. 


Cheers, Phillip

A A

unread,
Nov 12, 2016, 10:27:47 AM11/12/16
to RC2014-Z80


On Sunday, November 6, 2016 at 5:20:53 AM UTC-7, phillip.stevens wrote:

But now the bad news. I still haven't been able to get the linkage to the STDIO to work properly.

I'm afraid that I'm a bit out of my depth. The intermediate routines look like they're right.
I did change the input routine to use _acia_pollc to avoid needless IO reads from the ACIA device. But I think its ok.

rc_00_input_acia:

   cp ICHAR_MSG_GETC
   jr nz
, no_match_0

rc_00_input_acia_ichar_msg_getc
:

   
;  exit : a = keyboard char after character set translation
   
;         carry set on error, hl = 0 (stream error) or -1 (eof)
   
;
   
;  can use : af, bc, de, hl

block_loop
:

   call _acia_pollc            
; check whether any characters are in Rx buffer
   jr nc
, block_loop           ; if Rx buffer is empty
   
   call _acia_getc
   
   
; l = ascii code
   
   ld a
,l
   
   cp CHAR_CTRL_Z
   jp z
, error_mc              ; generate EOF (ctrl-z is from cp/m)
   
   
or a                        ; reset carry to indicate no error
   ret


It doesn't look like there's anything wrong with that.  I'll take another look when I am home tonight.
 

phillip.stevens

unread,
Nov 13, 2016, 5:56:15 AM11/13/16
to RC2014-Z80
Checking into this more. It is the printf() statement that doesn't work properly.
Character input through getchar() works fine.

The symptom for printf() failure is printing more than one character fails.
When printing a string the first character will be output properly, but then no more.


On Sunday, 13 November 2016 02:27:47 UTC+11, A A wrote:


On Sunday, November 6, 2016 at 5:20:53 AM UTC-7, phillip.stevens wrote:

But now the bad news. I still haven't been able to get the linkage to the STDIO to work properly.

I'm afraid that I'm a bit out of my depth. The intermediate routines look like they're right.
I did change the input routine to use _acia_pollc to avoid needless IO reads from the ACIA device. But I think its ok.
 
It doesn't look like there's anything wrong with that.  I'll take another look when I am home tonight.
 

A A

unread,
Nov 13, 2016, 12:30:10 PM11/13/16
to RC2014-Z80


On Sunday, November 13, 2016 at 3:56:15 AM UTC-7, phillip.stevens wrote:
Checking into this more. It is the printf() statement that doesn't work properly.
Character input through getchar() works fine.

The symptom for printf() failure is printing more than one character fails.
When printing a string the first character will be output properly, but then no more.

 The carry flag is used to indicate device error if the driver is unable to output a char.  It looks like the carry flag can be set at the end of acia_putc even after a successful putc:

target/rc2014/device/acia/acia_putc.asm

    _acia_putc:

        ; enter    : l = char to output
        ; exit     : l = 1 if Tx buffer is full
        ;            carry reset
        ; modifies : af, hl

....

        out (ACIA_CTRL_ADDR), a     ; set the ACIA CTRL register
       
        call asm_z80_pop_ei         ; critical section end

        ret


"asm_z80_pop_ei" can alter the carry flag.   You can add an "or a" just before the ret to clear the carry here or move that responsibility to the driver if having carry reset is not important here.


target/rc2014/driver/character/rc_00_output_acia.asm

.....


rc_00_output_acia_ochar_msg_putc:

   ;   enter   :  c = char
   ;   exit    : carry set if error
   ;   can use : af, bc, de, hl, af'
  
block_loop:

   ld l,c
   call _acia_putc
  
   dec l
   jr z, block_loop            ; if Tx buffer is full
  
   ret                         ; carry is reset

Add "or a" right before the ret.

I think that should do it.

A A

unread,
Nov 13, 2016, 12:52:54 PM11/13/16
to RC2014-Z80

It may also make sense to catch the flush message in the output driver.  In this case Tx is buffered so a program doing "fflush(stdout)" might expect the flush to block until the Tx buffer is emptied.  That could be done by busy-waiting until the Tx buffer is emptied (bad in a multithread situation) or by actively trying to deliver Tx chars until the buffer is empty without waiting on interrupts.

But this won't affect whether things work or not.

phillip.stevens

unread,
Nov 14, 2016, 6:35:01 PM11/14/16
to RC2014-Z80
The symptom for printf() failure is printing more than one character fails.
When printing a string the first character will be output properly, but then no more.

 The carry flag is used to indicate device error if the driver is unable to output a char.  It looks like the carry flag can be set at the end of acia_putc even after a successful putc:

target/rc2014/driver/character/rc_00_output_acia.asm

.....

rc_00_output_acia_ochar_msg_putc:

   ;   enter   :  c = char
   ;   exit    : carry set if error
   ;   can use : af, bc, de, hl, af'
  
block_loop:

   ld l,c
   call _acia_putc
  
   dec l
   jr z, block_loop            ; if Tx buffer is full
  
   ret                         ; carry is reset

Add "or a" right before the ret.

I think that should do it.

That did it!

I created a new separate repository, with the resulting code.

And I started a new thread, so we can continue the discussion there.

Hopefully, a few other people will test it and find it working too.

I've noted a few enhancements that I'd like to make in the repository issues section, but they can wait for a while.

Thanks again.



 
Reply all
Reply to author
Forward
0 new messages