Sunday, November 8, 2015

Board Works Without Arduino

I have been dealing with my board with its attached Arduino. And while I need the Arduino to unbrick the board, I don't need it for normal operation.

It took me a while to figure out what was going on. The behavior depended on whether the start up function did a run from RAM. That is, copy the current bank 0 into a RAM bank and then swap that RAM bank into bank 0. This is needed to program flash since programming flash requires no access from flash while it's programming. It is also necessary for setting breakpoints as a breakpoint is set by putting an 'rst 20h' instruction where the breakpoint belongs.

So when I was trying to run from RAM, it just bricked the computer. I finally recalled a choice I made to keep the CPLD from getting too complex. RESET changes the current banks, but I decided to rely on SW to set the next bank registers. But the runFromRAM function was just assuming that banks 2 and 3 were the same as the registers that specify the next banks 2 and 3. This is an invalid assumption. So, I added a memBankInit function that copies the current bank registers into the next bank registers.

Friday, October 30, 2015

Board Stopped Working

So, every so often my board decides to stop working. While it's a problem, solving problems is fun. I did load a new image into the 0 bank that was aggressively optimized. So it could be that. I did reset after loading, but neglected to power cycle. When I turned it on last night, nothing.

So I decided to restore an old image--but the Arduino program wasn't working. I am having trouble with wire wraps to bare copper wires. So I decided to redo the Arduino connector. I had been meaning to anyway, since I don't need the Arduino anymore except with this happens. I also wanted to mount an SD card slot for accessing an SD card which could potentially give be a whole chunk of flash depending on if I can drivers to fit. I probably would use as a block addressable device rather than with a file system. Fortunately, linux has a command called "dd" which would let me do that.

Here are pictures of the new sections. The first is the connector with the Arduino already connected with the red wires. It also has the SD card slot.


This is the back. I had to solder the wires to the connector and the insulation is a bit melted, so I'm hoping I won't have any shorts. Right now I don't.


Now, I'm off to see if I can get the flash to program again....

Wednesday, October 28, 2015

Disassembler in Action

I have a mechanism for attaching the disassembler to the breakpoint prompt. The applet in bank 1 has to have the disassember code because it's too big to fit with everything in bank 0. So, the address of the function is placed at 0x0024 and if that location is not 0, it is called. This has to be registered in main, so when the breakpoint occurs at the beginning of main, the disassember is not hooked up yet. But, see the output below. Once the debugger gets to 4074 disassembly is possible. The command is 'z' with optional number of instructions and then an optional start address. I still have a lot of diagnostic stuff in the breakpoint code that I'm ready to remove (e.g. "Set BP0 @ 4062 (6-0)" and "RST 20 to dd @ 4066").

Mark Hamann's Z80 Computer
App Build: 20:59:21 Oct  1 2015
BSP Build: 20:59:19 Oct  1 2015
C Lib Build: 20:08:20 Sep 30 2015
Menu
 1: dir
 2: run applet
 3: program intel hex
 4: utils menu
 5: drivers menu
> 3
In IHX program mode. Ensure that HW flow control is on.
Flash bank to program (0-f)? 4
Is this an applet (y/n)? n
Not erased. Erase (y/n)? y
Now, paste the .ihx contents
The mode will end on the :00000001FF
ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffDone!
> 2
Applet bank? (2-f)? c
Loading C vector bank 4 and applet bank c for version 0103 (ok=0)
Flash to RAM...
Prepping banks...
About to jump...
Set BP0 @ 4060 (6-0)
RST 20 to dd @ 4060
Breaking at main()
4060 e7
RST 20 from dd @ 4060
(brk @ 4060)> n
branch 00
Set BP0 @ 4062 (6-0)
RST 20 to dd @ 4062
RST 20 from dd @ 4062
(brk @ 4062)>
(brk @ 4062)> n
branch 00
Set BP0 @ 4066 (6-0)
RST 20 to dd @ 4066
RST 20 from dd @ 4066
(brk @ 4066)> n
branch 00
Set BP0 @ 4068 (6-0)
RST 20 to 21 @ 4068
RST 20 from 21 @ 4068
(brk @ 4068)> n
branch 00
Set BP0 @ 406b (6-0)
RST 20 to 39 @ 406b
RST 20 from 39 @ 406b
(brk @ 406b)> n
branch 00
Set BP0 @ 406c (6-0)
RST 20 to f9 @ 406c
RST 20 from f9 @ 406c
(brk @ 406c)> n
branch 00
Set BP0 @ 406d (6-0)
RST 20 to 21 @ 406d
RST 20 from 21 @ 406d
(brk @ 406d)> n
branch 00
Set BP0 @ 4070 (6-0)
RST 20 to e5 @ 4070
RST 20 from e5 @ 4070
(brk @ 4070)> z
(brk @ 4070)> n
branch 00
Set BP0 @ 4071 (6-0)
RST 20 to cd @ 4071
RST 20 from cd @ 4071
(brk @ 4071)> z
(brk @ 4071)> n
branch 18
Set BP0 @ 4074 (6-0)
RST 20 to 21 @ 4074
RST 20 from 21 @ 4074
(brk @ 4074)> z
4074: ld hl, 8080h
4077: ex (sp), hl
4078: call 3f23h
407b: pop af
407c: call 3f1dh
407f: ld hl, 007fh
4082: push hl
4083: call 3f11h
(brk @ 4074)> z 10 0
0: jp 0069h
3: rst 38h
4: rst 38h
5: rst 38h
6: rst 38h
7: rst 38h
8: jp 0db1h
b: rst 38h
c: rst 38h
d: rst 38h
e: rst 38h
f: rst 38h
10: jp 0dc2h
13: rst 38h
14: rst 38h
15: rst 38h
(brk @ 4074)> n
branch 00
Set BP0 @ 4077 (6-0)
RST 20 to e3 @ 4077
RST 20 from e3 @ 4077
(brk @ 4077)> z
4077: ex (sp), hl
4078: call 3f23h
407b: pop af
407c: call 3f1dh
407f: ld hl, 007fh
4082: push hl
4083: call 3f11h
4086: pop af
(brk @ 4077)> n
branch 00
Set BP0 @ 4078 (6-0)
RST 20 to cd @ 4078
RST 20 from cd @ 4078
(brk @ 4078)> n
branch 18
Set BP0 @ 407b (6-0)
RST 20 to f1 @ 407b
RST 20 from f1 @ 407b
(brk @ 407b)> z
407b: pop af
407c: call 3f1dh
407f: ld hl, 007fh
4082: push hl
4083: call 3f11h
4086: pop af
4087: ld hl, 0000h
408a: add hl, sp
(brk @ 407b)> c
branch 00
Set BP0 @ 407c (a-1)
RST 20 to cd @ 407c
RST 20 from cd @ 407c

OK, I cleaned up the diagnostic stuff. Here is what it looks like now:

Menu
 1: dir
 2: run applet
 3: program intel hex
 4: utils menu
 5: drivers menu
> 3
In IHX program mode. Ensure that HW flow control is on.
Flash bank to program (0-f)? 4
Is this an applet (y/n)? n
Not erased. Erase (y/n)? y
Now, paste the .ihx contents
The mode will end on the :00000001FF
fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffDone!
> 2
Applet bank? (2-f)? c
Loading C vector bank 4 and applet bank c for version 0103 (ok=0)
Flash to RAM...
Prepping banks...
About to jump...
Breaking at main()
4060 e7
(brk @ 4060)> n
(brk @ 4062)> n
(brk @ 4066)> n
(brk @ 4068)> n
(brk @ 406b)> n
(brk @ 406c)> n
(brk @ 406d)> n
(brk @ 4070)> n
(brk @ 4071)> n
(brk @ 4074)> n
(brk @ 4077)> z
4077: ex (sp), hl
4078: call 3f23h
407b: pop af
407c: call 3f1dh
407f: ld hl, 007fh
4082: push hl
4083: call 3f11h
4086: pop af
(brk @ 4077)> n
(brk @ 4078)> n
(brk @ 407b)> n
(brk @ 407c)> n
(brk @ 407f)> n
(brk @ 4082)> n
(brk @ 4083)> n
(brk @ 4086)> z
4086: pop af
4087: ld hl, 0000h
408a: add hl, sp
408b: ld d, l
408c: ld e, h
408d: ld bc, 40c7h
4090: push hl
4091: push de
(brk @ 4086)>

Monday, October 26, 2015

Disassembly

In order to make debugging easier, I thought it might to have a disassembler. It's fairly table driven. It needs a lot of work still. I think it disassembles valid instructions, but doesn't guarantee that invalid instructions are handled in any particular way. By valid, I mean the officially documented instructions. It is not optimized for speed. Rather, my goal was code size. Whether I met that or not is something I'm wondering. All the little special cases add code size.

Anyone needing a Z-80 disassembler is free to use what they want from this.


// Associate shiftMask to a StartInfo
struct ShiftMaskAndIndexStartInfo {
    // upper nibble is shift amount. Bottom nibble is mask
    uint8_t shiftMaskInfo;

    // A start index
    uint8_t indexStart;
};

// Information to create an instruction
struct InstructionInfo
{
    // 8 bit mask of relevant bits
    uint8_t mask;

    // expected value after anding with mask
    uint8_t value;

    // 7-6: type  5-0: index
    uint8_t opcodeInfo;

    // arg0 info
    uint8_t argInfoIndex0;

    // arg1 info
    uint8_t argInfoIndex1;
};

// Opcode builder
struct OpcodeBuildInfo {
    // 8 bit mask of relevant bits
    uint8_t mask;

    // Expected value after anding with mask
    uint8_t value;

    // Letter to append if (b & mask) == value
    char letter;
};


// Keep synched with InstructionTableArray
enum InstructionTableIndices
{
    TABLE_MAIN_00,
    TABLE_MAIN_40,
    TABLE_MAIN_80,
    TABLE_MAIN_C0,
    TABLE_CB,
    TABLE_ED,
    TABLE_COUNT
};

// keep synched with LiteralStringArray
enum LiteralStringIndices {
    DA_LIT_Q,
    DA_LIT_nop,
    DA_LIT_djnz,
    DA_LIT_jr,
    DA_LIT_ld,
    DA_LIT_add,
    DA_LIT_ex,
    DA_LIT_halt,
    DA_LIT_ret,
    DA_LIT_call,
    DA_LIT_jp,
    DA_LIT_exx,
    DA_LIT_rst,
    DA_LIT_neg,
    DA_LIT_reti,
    DA_LIT_retn,
    DA_LIT_im,
    DA_LIT_inc,
    DA_LIT_dec,
    DA_LIT_adc,
    DA_LIT_sub,
    DA_LIT_sbc,
    DA_LIT_and,
    DA_LIT_xor,
    DA_LIT_or,
    DA_LIT_cp,
    DA_LIT_daa,
    DA_LIT_cpl,
    DA_LIT_scf,
    DA_LIT_ccf,
    DA_LIT_pop,
    DA_LIT_push,
    DA_LIT_out,
    DA_LIT_in,
    DA_LIT_di,
    DA_LIT_ei,
    DA_LIT_bit,
    DA_LIT_set,
    DA_LIT_res,
    DA_LIT_nz,
    DA_LIT_z,
    DA_LIT_nc,
    DA_LIT_c,
    DA_LIT_po,
    DA_LIT_pe,
    DA_LIT_p,
    DA_LIT_m,
    DA_LIT_bc,
    DA_LIT_de,
    DA_LIT_hl,
    DA_LIT_ix,
    DA_LIT_iy,
    DA_LIT_sp,
    DA_LIT_af,
    DA_LIT_a,
    DA_LIT_b,
    DA_LIT_d,
    DA_LIT_e,
    DA_LIT_h,
    DA_LIT_l,
    DA_LIT_i,
    DA_LIT_r,
    DA_LIT_af_alt,
    DA_LIT_COUNT
};

// keep synced with enum DisassembleLiteralIndices
static const char* const LiteralStringArray[DA_LIT_COUNT] =
{
    "?",
    "nop",
    "djnz",
    "jr",
    "ld",
    "add",
    "ex",
    "halt",
    "ret",
    "call",
    "jp",
    "exx",
    "rst",
    "neg",
    "reti",
    "retn",
    "im",
    "inc",
    "dec",
    "adc",
    "sub",
    "sbc",
    "and",
    "xor",
    "or",
    "cp",
    "daa",
    "cpl",
    "scf",
    "ccf",
    "pop",
    "push",
    "out",
    "in",
    "di",
    "ei",
    "bit",
    "set",
    "res",
    "nz",
    "z",
    "nc",
    "c",
    "po",
    "pe",
    "p",
    "m",
    "bc",
    "de",
    "hl",
    "ix",
    "iy",
    "sp",
    "af",
    "a",
    "b",
    "d",
    "e",
    "h",
    "l",
    "i",
    "r",
    "af'"
};

/*
0: @alu : add, adc, sub, sbc, and, xor, or, cp
1 : @bitsetres : -, bit, set, res
2 : @daacplscfccf : daa, cpl, scf, ccf
3 : @incdec : inc, dec
4 : @poppush : pop, push
5 : @outin : out, in
6 : @inout : in, out
7 : @diei : di, ei
8 : @sbcadc : sbc, adc
*/

enum OpcodeByMaskInfoIndices
{
    OPCODE_MASKABLE_ALU, // 10@@ @rrr -- 11@@ @110
    OPCODE_MASKABLE_BIT, // @@## #rrr
    OPCODE_MASKABLE_DAA, // 001@ @111
    OPCODE_MASKABLE_INC_RRS, // 00rr @011
    OPCODE_MASKABLE_INC_R, // 00rr r10@
    OPCODE_MASKABLE_POP, // 11rr 0@01
    OPCODE_MASKABLE_INOUT, // 01rr r00@
    OPCODE_MASKABLE_OUTIN, // 1110 @011
    OPCODE_MASKABLE_DIEI, // 1111 @011
    OPCODE_MASKABLE_SBC, // 01rr @010
    OPCODE_MASKABLE_COUNT,
};

enum OpcodeByMaskStartIndices
{
    MASKABLE_ALU = 0,
    MASKABLE_BIT = MASKABLE_ALU + 8-1, // first element of bit can't exist, so collapse
    MASKABLE_DAA = MASKABLE_BIT + 4,
    MASKABLE_INC = MASKABLE_DAA + 4,
    MASKABLE_POP = MASKABLE_INC + 2,
    MASKABLE_INOUT = MASKABLE_POP + 2,
    MASKABLE_OUTIN = MASKABLE_INOUT + 2 - 1,
    MASKABLE_DIEI = MASKABLE_OUTIN + 2,
    MASKABLE_SBC = MASKABLE_DIEI + 2,
    MASKABLE_ARRAY_COUNT = MASKABLE_SBC + 2
};

static const struct ShiftMaskAndIndexStartInfo OpcodeByMaskShiftMaskArray[OPCODE_MASKABLE_COUNT] =
{
    {
        0x37, MASKABLE_ALU
    },
    {
        0x63, MASKABLE_BIT
    },
    {
        0x33, MASKABLE_DAA
    },
    { // r
        0x31, MASKABLE_INC
    },
    { // rrs
        0x01, MASKABLE_INC
    },
    {
        0x21, MASKABLE_POP
    },
    {
        0x01, MASKABLE_INOUT
    },
    {
        0x31, MASKABLE_OUTIN
    },
    {
        0x31, MASKABLE_DIEI
    },
    {
        0x31, MASKABLE_SBC
    },
};

static const uint8_t OpcodeByMaskArray[MASKABLE_ARRAY_COUNT] =
{
    // MASKABLE_ALU = 0
    DA_LIT_add, // 0
    DA_LIT_adc,
    DA_LIT_sub,
    DA_LIT_sbc,
    DA_LIT_and,
    DA_LIT_xor,
    DA_LIT_or,
    // MASKABLE_BIT = 7
    DA_LIT_cp,

    DA_LIT_bit, // 8
    DA_LIT_res,
    DA_LIT_set,

    // MASKABLE_DAA
    DA_LIT_daa, // 11
    DA_LIT_cpl,
    DA_LIT_scf,
    DA_LIT_ccf,

    // MASKABLE_INC
    DA_LIT_inc, // 15
    DA_LIT_dec,

    // MASKABLE_POP
    DA_LIT_pop, // 17
    DA_LIT_push,

    // MASKABLE_INOUT
    DA_LIT_in, // 19
    // MASKABLE_OUTIN
    DA_LIT_out, // 20
    DA_LIT_in,

    // MASKABLE_EIDI
    DA_LIT_di, // 22
    DA_LIT_ei,

    // MASKABLE_SBC
    DA_LIT_sbc, // 24
    DA_LIT_adc
};

// ROTATE Main-00-00; ED-80-00; CB-02-00 "r"
// SHIFT CB-02-02 "s"
// LD ED-07-00 "ld"
// CP ED-07-01 "cp"
// IN ED-07-02 "in"
// OT ED-07-03 "ot
// CARRY MAIN/CB-01-01 "c"
// LOGIGAL CB-03-03 "l"
// ARITH/Acc CB-03-02 "a"
// LEFT Main-08-00; "l"
// RIGHT Main-08-08; "r"
// INC ED-88-80 "i"
// DEC ED-88-88 "d"
// REPEAT ED-f0-b0 "r"

enum OpcodeByBuilderIndices
{
    OPCODE_BUILD_RLCA,
    OPCODE_BUILD_RRD,
    OPCODE_BUILD_LDI,
    OPCODE_BUILD_RLC
};

// sync with OpcodeBuilderArray and OpcodeByBuilderIndices
static const uint8_t OpcodeBuilderIndexToOpcodeBuilderArrayStartIndex[] = {
    0,
    6,
    11,
    24,
    33
};

// synch with OpcodeBuilderIndexToOpcodeBuilderArrayStartIndex
static const struct OpcodeBuildInfo OpcodeBuilderArray[32] = {
    //MAIN table
    //{
    // main = 0 (5)
    { 0x00, 0x00, 'r' },
    { 0x08, 0x00, 'l' },
    { 0x08, 0x08, 'r' },
    { 0x10, 0x00, 'c' },
    { 0x00, 0x00, 'a' },
    { 0x00, 0x00, 0 },
    //},
    // ED table
    //{
    { 0x00, 0x00, 'r' },
    { 0xc8, 0x40, 'r' },
    { 0xc8, 0x48, 'l' },
    { 0x00, 0x00, 'd' },
    { 0x00, 0x00, 0 },
    //},
    //{
    { 0xc7, 0x80, 'l' },
    { 0xc7, 0x80, 'd' },
    { 0xc7, 0x81, 'c' },
    { 0xc7, 0x81, 'p' },
    { 0xc7, 0x82, 'i' },
    { 0xc7, 0x82, 'n' },
    { 0xc7, 0x83, 'o' },
    { 0xf7, 0xa3, 'u' },
    { 0xc7, 0x83, 't' },
    { 0x88, 0x80, 'i' },
    { 0x88, 0x88, 'd' },
    { 0xf0, 0xb0, 'r' },
    { 0x00, 0x00, 0 },
    //},
    // CB table
    //{
    { 0x20, 0x00, 'r' },
    { 0x20, 0x20, 's' },
    { 0x08, 0x00, 'l' },
    { 0x08, 0x08, 'r' },
    { 0x30, 0x00, 'c' },
    { 0x30, 0x20, 'a' },
    { 0x30, 0x30, 'l' },
    { 0x00, 0x00, 0 },
    //}
};


#define ARG_TYPE_SPECIAL    (0x00)
#define ARG_TYPE_LIT        (0x10)
#define ARG_TYPE_MASK       (0x20)
#define ARG_TYPE_SWAPPABLE  (0x40)
#define ARG_TYPE_CONTENTS   (0x80)

enum ArgByMaskStartIndices
{
    ARG_ARR_FL = 0,
    ARG_ARR_RRS = ARG_ARR_FL + 8,
    ARG_ARR_RRA = ARG_ARR_RRS + 4,
    ARG_ARR_HLA = ARG_ARR_RRA + 4,
    ARG_ARR_R = ARG_ARR_HLA + 2,
    ARG_ARR_IR = ARG_ARR_R + 8,
    ARG_ARR_IM = ARG_ARR_IR + 2,
    ARG_ARR_COUNT = ARG_ARR_IM + 4
};

static const uint8_t ArgByMaskArray[ARG_ARR_COUNT] =
{
    DA_LIT_nz, // 0
    DA_LIT_z,
    DA_LIT_nc,
    DA_LIT_c,
    DA_LIT_po,
    DA_LIT_pe,
    DA_LIT_p,
    DA_LIT_m,

    DA_LIT_bc, // 8
    DA_LIT_de,
    DA_LIT_hl,
    DA_LIT_sp,

    DA_LIT_bc, // 12
    DA_LIT_de,
    DA_LIT_hl,
    DA_LIT_af,

    DA_LIT_hl, // 16
    DA_LIT_a,

    DA_LIT_b, // 18
    DA_LIT_c,
    DA_LIT_d,
    DA_LIT_e,
    DA_LIT_h,
    DA_LIT_l,
    DA_LIT_hl | ARG_TYPE_CONTENTS,
    DA_LIT_a,

    DA_LIT_i, // 42
    DA_LIT_r,
};

/*
// 
#define ARG_TYPE_MASK (0x01)
// mask, index
#define ARG_TYPE_LIT (0x02)
// (int8_t)(*(pc)) + pc; pc++;
#define ARG_TYPE_REL (0x03)
#define ARG_TYPE_IMM8 (0x04)
#define ARG_TYPE_IMM16 (0x05)
#define ARG_TYPE_ASCII (0x06)
#define ARG_TYPE_RST (0x07)
*/

// index to next table ED/CB/DDFD
#define OPCODE_TYPE_LIT (0x00)
// index
#define OPCODE_TYPE_ARR (0x40)
// mask, index
#define OPCODE_TYPE_BUILD (0x80)
// index
#define OPCODE_TYPE_SUB (0xc0)


// [8:mask],[8:val],[2:opcode, 6:index0] [1: swappable 3: arg0 3: arg1]
// index 0 is literal index for literal, or maskables index for maskable. For build, the build index
// swapbit is there only if swappable. Swap bit is 3 for main, 4 for ED
// arginfo is:
//  if 00 no arg
//  if 01 mask [8: mask(-sss-mmm)] [8: arg_arr index] or 3bit is mask index, 5 bits is arg_arr index
//  if 02 lit [8: lit index]
//  if 03 uint8 get next byte
//  if 04 RelAddr get next byte, add to location
//  if 05 uint16 get next word
//  bit 8 swappable
enum ArgInfo {
    ARG_INFO_NONE,
    ARG_INFO_rel, // 1
    ARG_INFO_imm8,
    ARG_INFO_imm16,
    ARG_INFO_bit,
    ARG_INFO_rst,
    ARG_INFO_im,

    ARG_INFO_LIT_a = ARG_TYPE_LIT, // 4
    ARG_INFO_LIT_af,
    ARG_INFO_LIT_af_alt,
    ARG_INFO_LIT_c,
    ARG_INFO_LIT_de,

    ARG_INFO_LIT_hl, // 8
    ARG_INFO_LIT_sp,
    //ARG_INFO_LIT_q,

    ARG_INFO_MASK_30_RRS = ARG_TYPE_MASK,
    ARG_INFO_MASK_30_RRA,
    ARG_INFO_MASK_07_R,

    ARG_INFO_MASK_38_R,
    ARG_INFO_MASK_18_FL,
    ARG_INFO_MASK_38_FL,
    ARG_INFO_MASK_08_IR,
};

/*
enum ArgMaskIndex {
    ARG_MASK_30_RRS,
    ARG_MASK_30_RRA,
    ARG_MASK_07_R,
    ARG_MASK_38_R,

    ARG_MASK_30_FL,
    ARG_MASK_38_FL,
    ARG_MASK_38_RST,
    ARG_MASK_80_IR,

    ARG_MASK_COUNT,
};
*/

// sync with ArgInfo from ARG_TYPE_LIT
static const uint8_t ArgByLitIndexToStringLiteralIndex[] = {
    DA_LIT_a,
    DA_LIT_af,
    DA_LIT_af_alt,
    DA_LIT_c,

    DA_LIT_de,
    DA_LIT_hl,
    DA_LIT_sp
};

// sync with ArgInfo from ARG_TYPE_MASK
static const struct ShiftMaskAndIndexStartInfo ArgShiftMaskStartIndex[] =
{
    { /*0x30*/ 0x43, ARG_ARR_RRS },
    { /*0x30*/ 0x43, ARG_ARR_RRA },
    { /*0x07*/ 0x07, ARG_ARR_R },
    { /*0x38*/ 0x37, ARG_ARR_R },

    { /*0x18*/ 0x33, ARG_ARR_FL },
    { /*0x38*/ 0x37, ARG_ARR_FL },
    { /*0x08*/ 0x31, ARG_ARR_IR },
};

char* buildOpcode(uint8_t opcode, uint8_t table, char* line)
{
    const struct OpcodeBuildInfo* cand = &OpcodeBuilderArray[OpcodeBuilderIndexToOpcodeBuilderArrayStartIndex[table]];
    while (cand->letter != 0)
    {
        if ((opcode & cand->mask) == cand->value)
        {
            *line++ = cand->letter;
        }
        cand++;
    }
    *line++ = '\0';
    return line;
}

static const struct InstructionInfo InstructionInfoTableMain_00[] =
{
    {
        0xff,
        0x00,
        DA_LIT_nop | OPCODE_TYPE_LIT,
        ARG_INFO_NONE,
        ARG_INFO_NONE,
    },
    {
        0xff,
        0x10,
        DA_LIT_djnz | OPCODE_TYPE_LIT,
        ARG_INFO_rel,
        ARG_INFO_NONE
    },
    {
        0xe7,
        0x20,
        DA_LIT_jr | OPCODE_TYPE_LIT,
        ARG_INFO_MASK_18_FL,
        ARG_INFO_rel,
    },
    {
        0xcf,
        0x01,
        DA_LIT_ld | OPCODE_TYPE_LIT,
        ARG_INFO_MASK_30_RRS,
        ARG_INFO_imm16
    },
    {
        0xe7,
        0x02,
        DA_LIT_ld | OPCODE_TYPE_LIT,
        ARG_INFO_MASK_30_RRS | ARG_TYPE_SWAPPABLE | ARG_TYPE_CONTENTS, // swap on 0x08
        ARG_INFO_LIT_a
    },
    {
        0xcf,
        0x09,
        DA_LIT_add | OPCODE_TYPE_LIT,
        ARG_INFO_LIT_hl,
        ARG_INFO_MASK_30_RRS
    },
    {
        0xf7,
        0x32,
        DA_LIT_ld | OPCODE_TYPE_LIT,
        ARG_INFO_imm16 | ARG_TYPE_SWAPPABLE | ARG_TYPE_CONTENTS, // swap on 0x08
        ARG_INFO_LIT_a
    },
    {
        0xf7,
        0x22,
        DA_LIT_ld | OPCODE_TYPE_LIT,
        ARG_INFO_imm16 | ARG_TYPE_SWAPPABLE | ARG_TYPE_CONTENTS, // swap on 0x08
        ARG_INFO_LIT_hl
    },
    {
        0xc7,
        0x03,
        OPCODE_MASKABLE_INC_RRS | OPCODE_TYPE_ARR,
        ARG_INFO_MASK_30_RRS,
        ARG_INFO_NONE
    },
    {
        0xc6,
        0x04,
        OPCODE_MASKABLE_INC_R | OPCODE_TYPE_ARR,
        ARG_INFO_MASK_38_R,
        ARG_INFO_NONE
    },
    {
        0xc7,
        0x06,
        DA_LIT_ld | OPCODE_TYPE_LIT,
        ARG_INFO_MASK_38_R,
        ARG_INFO_imm8
    },
    {
        0xff,
        0x08,
        DA_LIT_ex | OPCODE_TYPE_LIT,
        ARG_INFO_LIT_af,
        ARG_INFO_LIT_af_alt
    },
    {
        0xff,
        0x18,
        DA_LIT_jr | OPCODE_TYPE_LIT,
        ARG_INFO_rel,
        ARG_INFO_NONE
    },
    {
        0xe7,
        0x27,
        OPCODE_MASKABLE_DAA | OPCODE_TYPE_ARR,
        ARG_INFO_NONE,
        ARG_INFO_NONE
    },
    {
        0xe7,
        0x07,
        OPCODE_BUILD_RLCA | OPCODE_TYPE_BUILD,
        ARG_INFO_NONE,
        ARG_INFO_NONE
    },
    {
        0x00,
        0x00,
        DA_LIT_Q | OPCODE_TYPE_LIT,
        ARG_INFO_NONE,
        ARG_INFO_NONE
    }
};
static const struct InstructionInfo InstructionInfoTableMain_40[] =
{
    { // Must be before ld r,r
        0xff,
        0x76,
        DA_LIT_halt | OPCODE_TYPE_LIT,
        ARG_INFO_NONE,
        ARG_INFO_NONE
    },

    { //  Must be after halt
        0xc0,
        0x40,
        DA_LIT_ld | OPCODE_TYPE_LIT,
        ARG_INFO_MASK_38_R,
        ARG_INFO_MASK_07_R
    },
    {
        0x00,
        0x00,
        DA_LIT_Q | OPCODE_TYPE_LIT,
        ARG_INFO_NONE,
        ARG_INFO_NONE
    }
};
static const struct InstructionInfo InstructionInfoTableMain_80[] =
{
    {
        0xc0,
        0x80,
        OPCODE_MASKABLE_ALU | OPCODE_TYPE_ARR,
        ARG_INFO_LIT_a,
        ARG_INFO_MASK_07_R
    },
    {
        0x00,
        0x00,
        DA_LIT_Q | OPCODE_TYPE_LIT,
        ARG_INFO_NONE,
        ARG_INFO_NONE
    }
};
static const struct InstructionInfo InstructionInfoTableMain_c0[] =
{
    {
        0xc7,
        0xc0,
        DA_LIT_ret | OPCODE_TYPE_LIT,
        ARG_INFO_MASK_38_FL,
        ARG_INFO_NONE
    },
    {
        0xff,
        0xcd,
        DA_LIT_call | OPCODE_TYPE_LIT,
        ARG_INFO_imm16,
        ARG_INFO_NONE
    },
    {
        0xff,
        0xc3,
        DA_LIT_jp | OPCODE_TYPE_LIT,
        ARG_INFO_imm16,
        ARG_INFO_NONE
    },
    {
        0xff,
        0xe9,
        DA_LIT_jp | OPCODE_TYPE_LIT,
        ARG_INFO_LIT_hl | ARG_TYPE_CONTENTS,
        ARG_INFO_NONE
    },
    {
        0xff,
        0xf9,
        DA_LIT_ld | OPCODE_TYPE_LIT,
        ARG_INFO_LIT_sp,
        ARG_INFO_LIT_hl
    },
    {
        0xcb,
        0xc1,
        OPCODE_MASKABLE_POP | OPCODE_TYPE_ARR,
        ARG_INFO_MASK_30_RRA,
        ARG_INFO_NONE
    },
    {
        0xff,
        0xc9,
        DA_LIT_ret | OPCODE_TYPE_LIT,
        ARG_INFO_NONE,
        ARG_INFO_NONE
    },
    {
        0xff,
        0xd9,
        DA_LIT_exx | OPCODE_TYPE_LIT,
        ARG_INFO_NONE,
        ARG_INFO_NONE
    },
    {
        0xc7,
        0xc2,
        DA_LIT_jp | OPCODE_TYPE_LIT,
        ARG_INFO_MASK_38_FL,
        ARG_INFO_imm16
    },
    {
        0xc7,
        0xc4,
        DA_LIT_call | OPCODE_TYPE_LIT,
        ARG_INFO_MASK_38_FL,
        ARG_INFO_imm16
    },
    {
        0xc7,
        0xc7,
        DA_LIT_rst | OPCODE_TYPE_LIT,
        ARG_INFO_rst,
        ARG_INFO_NONE
    },
    {
        0xc7,
        0xc6,
        OPCODE_MASKABLE_ALU | OPCODE_TYPE_ARR,
        ARG_INFO_LIT_a,
        ARG_INFO_imm8
    },
    {
        0xf7,
        0xd3,
        OPCODE_MASKABLE_OUTIN | OPCODE_TYPE_ARR,
        ARG_INFO_imm8 | ARG_TYPE_CONTENTS | ARG_TYPE_SWAPPABLE, // swap on 0x08
        ARG_INFO_LIT_a
    },
    {
        0xf7,
        0xf3,
        OPCODE_MASKABLE_DIEI | OPCODE_TYPE_ARR,
        ARG_INFO_NONE,
        ARG_INFO_NONE
    },
    {
        0xff,
        0xe3,
        DA_LIT_ex | OPCODE_TYPE_LIT,
        ARG_INFO_LIT_sp | ARG_TYPE_CONTENTS,
        ARG_INFO_LIT_hl
    },
    {
        0xff,
        0xeb,
        DA_LIT_ex | OPCODE_TYPE_LIT,
        ARG_INFO_LIT_de,
        ARG_INFO_LIT_hl
    },
    {
        0xff,
        0xcb,
        TABLE_CB | OPCODE_TYPE_SUB,
        ARG_INFO_NONE,
        ARG_INFO_NONE
    },
    {
        0xff,
        0xed,
        TABLE_ED | OPCODE_TYPE_SUB,
        ARG_INFO_NONE,
        ARG_INFO_NONE
    },
    {
        0xfd,
        0xfd,
        0 | OPCODE_TYPE_SUB,
        ARG_INFO_NONE,
        ARG_INFO_NONE
    },
    {
        0x00,
        0x00,
        DA_LIT_Q | OPCODE_TYPE_LIT,
        ARG_INFO_NONE,
        ARG_INFO_NONE
    }
};

static const struct InstructionInfo InstructionInfoTableCB[] =
{
    // CB
    {
        0xc0,
        0x00,
        OPCODE_BUILD_RLC | OPCODE_TYPE_BUILD,
        ARG_INFO_MASK_07_R,
        ARG_INFO_NONE
    },
    {
        0x00,
        0x00,
        OPCODE_MASKABLE_BIT | OPCODE_TYPE_ARR,
        ARG_INFO_bit,
        ARG_INFO_MASK_07_R
    },
    {
        0x00,
        0x00,
        DA_LIT_Q | OPCODE_TYPE_LIT,
        ARG_INFO_NONE,
        ARG_INFO_NONE
    }
};

static const struct InstructionInfo InstructionInfoTableED[] =
{
    // ED
    {
        0xc6,
        0x40,
        OPCODE_MASKABLE_INOUT | OPCODE_TYPE_ARR,
        ARG_INFO_MASK_38_R | ARG_TYPE_SWAPPABLE, // TODO swap on 0x01
        ARG_INFO_LIT_c | ARG_TYPE_CONTENTS
    },
    {
        0xc7,
        0x42,
        OPCODE_MASKABLE_SBC | OPCODE_TYPE_ARR,
        ARG_INFO_LIT_hl,
        ARG_INFO_MASK_30_RRS
    },
    {
        0xc7,
        0x43,
        DA_LIT_ld | OPCODE_TYPE_LIT,
        ARG_INFO_imm16 | ARG_TYPE_CONTENTS | ARG_TYPE_SWAPPABLE, // TODO swap in 0x08
        ARG_INFO_MASK_30_RRS
    },
    {
        0xc7,
        0x44,
        DA_LIT_neg | OPCODE_TYPE_LIT,
        ARG_INFO_NONE,
        ARG_INFO_NONE
    },
    {
        0xff,
        0x4d,
        DA_LIT_reti | OPCODE_TYPE_LIT,
        ARG_INFO_NONE,
        ARG_INFO_NONE
    },
    {
        0xc7,
        0x45,
        DA_LIT_retn | OPCODE_TYPE_LIT,
        ARG_INFO_NONE,
        ARG_INFO_NONE
    },
    {
        0xe7,
        0x47,
        DA_LIT_ld | OPCODE_TYPE_LIT,
        ARG_INFO_MASK_08_IR | ARG_TYPE_SWAPPABLE, // TODO swap not working -- on bit 4 0x10
        ARG_INFO_LIT_a
    },
    {
        0xf7,
        0x67,
        OPCODE_BUILD_RRD | OPCODE_TYPE_BUILD,
        ARG_INFO_NONE,
        ARG_INFO_NONE
    },
    {
        0xc7,
        0x46,
        DA_LIT_im | OPCODE_TYPE_LIT,
        ARG_INFO_im,
        ARG_INFO_NONE
    },
    {
        0xe4,
        0xa0,
        OPCODE_BUILD_LDI | OPCODE_TYPE_BUILD,
        ARG_INFO_NONE,
        ARG_INFO_NONE
    },
    {
        0x00,
        0x00,
        DA_LIT_Q | OPCODE_TYPE_LIT,
        ARG_INFO_NONE,
        ARG_INFO_NONE
    }
};

// sync with InstructionTableIndices
static const struct InstructionInfo* InstructionInfoTableArray[TABLE_COUNT] =
{
    InstructionInfoTableMain_00,
    InstructionInfoTableMain_40,
    InstructionInfoTableMain_80,
    InstructionInfoTableMain_c0,
    InstructionInfoTableCB,
    InstructionInfoTableED
};

uint8_t shiftMaskToByte(uint8_t b, uint8_t shiftMaskByte)
{
    return (b >> (shiftMaskByte >> 4)) & shiftMaskByte & 0x0f;
}

static const char IM_0Q12[4] = { '0', '?', '1', '2' };
static const char* const FORMAT1C = "%c";
static const char* const FORMAT1 = "%01xh";
static const char* const FORMAT2 = "%02xh";
static const char* const FORMAT4 = "%04xh";
static const char* const FORMAT_CONTENTS = "(%s)";

uint8_t* disassemble(uint8_t* loc, char* line)
{
    uint8_t b; // instruction byte being disassembled
    uint8_t table = 0;
    const struct InstructionInfo* entry;
    bool swap = false;
    uint8_t argCount = 0;
    char arg0[8];
    char arg1[8];
    char* args[2] = { arg0, arg1 };
    uint8_t opcodeInfo;
    uint8_t opcodeIndex;
    uint16_t offset;
    uint8_t hl_ix_iy = 0;
    char offsetStr[6] = { 0 };

    *line = 0;
    b = *loc++;
    table = b >> 6;

    // See if we need a special table
    // 1100 1011 cb
    // 1110 1101 ed
    // 11-0 1--1
    // 1101 1101
    // 1111 1101
    // 11-1 1101
    if ((b & 0xdf) == 0xdd)
    {
        if (b == 0xdd)
        {
            b = *loc++;
            table = b >> 6;
            hl_ix_iy = 1;
        }
        else if (b == 0xfd)
        {
            b = *loc++;
            table = b >> 6;
            hl_ix_iy = 2;
        }
    }
    if ((b & 0xd9) == 0xc9)
    {
        if (b == 0xcb)
        {
            b = *loc++;
            table = TABLE_CB;
        }
        else if (b == 0xed)
        {
            b = *loc++;
            table = TABLE_ED;
        }
    }

    entry = InstructionInfoTableArray[table];
    while (entry->mask != 0)
    {
        uint8_t opcodeType;
        if ((b & entry->mask) == entry->value)
        {
            break;
        }
        entry++;
    }

    // got here so we found something to process
    opcodeInfo = entry->opcodeInfo;
    opcodeIndex = entry->opcodeInfo & 0x3f;
    if ((opcodeInfo & 0x80) == 0)
    {
        if ((opcodeInfo & 0x40) == 0)
        {
            // OPCODE_TYPE_LIT
            strcpy(line, LiteralStringArray[opcodeIndex]);
        }
        else
        {
            // OPCODE_TYPE_ARR
            const struct ShiftMaskAndIndexStartInfo* opcodeByMaskInfo = &OpcodeByMaskShiftMaskArray[opcodeIndex];
            uint8_t index = opcodeByMaskInfo->indexStart + shiftMaskToByte(b, opcodeByMaskInfo->shiftMaskInfo);
            strcpy(line, LiteralStringArray[OpcodeByMaskArray[index]]);
        }
    }
    else
    {
        // OPCODE_TYPE_BIT
        buildOpcode(b, opcodeIndex, line);
    }

    for(argCount = 0; argCount < 2; argCount++)
    {
        uint8_t argInfo = (&entry->argInfoIndex0)[argCount];
        char buffer[8];
        const char* argText;
        char* arg = args[argCount];
        uint16_t argData;
        const char* format = NULL;
        uint8_t lit = 0xff;
        bool contents = false;

        // no more arguments
        if (argInfo == 0)
        {
            break;
        }

        // extra info --swappable/contents
        if ( ((argInfo & ARG_TYPE_SWAPPABLE) != 0) && !swap)
        {
            if (table < 4)
            {
                swap = (b & 0x08) != 0;
            }
            else if (table == TABLE_ED)
            {
                uint8_t swapMask;
                // entryMask bit
                //   c6       0/0x01
                //   c7       3/0x08
                //   e7       4/0x10
                if ((entry->mask & 0x20) == 0)
                {
                    if ((entry->mask & 0x01) == 0)
                    {
                        swapMask = 0x01;
                    }
                    else
                    {
                        swapMask = 0x08;
                    }
                }
                else
                {
                    swapMask = 0x10;
                }
                swap = (b & swapMask) != 0;
            }
        }
        contents = (argInfo & ARG_TYPE_CONTENTS) != 0;

        argInfo &= 0x3f; // remove swap/contents bits
        // either set lit or argText
        if (argInfo < ARG_TYPE_LIT)
        {
            // Specials
            switch (argInfo & 0x07)
            {
            case ARG_INFO_rel:
                argData = (uint16_t)(loc - 1) + (int8_t)(*loc);
                loc++;
                format = FORMAT4;
                break;
            case ARG_INFO_imm8:
                argData = (uint8_t)(*loc);
                loc++;
                format = FORMAT2;
                break;
            case ARG_INFO_imm16:
                argData = *(uint16_t*)loc;
                loc += 2;
                format = FORMAT4;
                break;
            case ARG_INFO_bit:
                argData = (b >> 3) & 0x07;
                format = FORMAT1;
                break;
            case ARG_INFO_rst:
                argData = (b & 0x38);
                format = FORMAT2;
                break;
            case ARG_INFO_im:
                argData = IM_0Q12[(b & 0x18) >> 3];
                format = FORMAT1C;
                break;
            }
            sprintf(buffer, format, argData);
            argText = &buffer[0];
        }
        else if (argInfo < ARG_TYPE_MASK)
        {
            // Lit
            lit = ArgByLitIndexToStringLiteralIndex[argInfo & 0x0f];
        }
        else
        {
            // mask
            const struct ShiftMaskAndIndexStartInfo* maskInfo = &ArgShiftMaskStartIndex[argInfo & 0x0f];
            //printf("MASKINGO: %02x b: %02x -> %d\r\n", maskInfo->shiftMaskInfo, b, shiftMaskToByte(b, maskInfo->shiftMaskInfo));
            lit = ArgByMaskArray[maskInfo->indexStart + shiftMaskToByte(b, maskInfo->shiftMaskInfo)];
        }

        // indexed lit might have the contents flag set
        if ((lit != 0xff) && ((lit & ARG_TYPE_CONTENTS) != 0))
        {
            lit &= 0x3f;
            contents = true;
        }

        // Convert DD/FD, hl to ix/iy and (hl) to (ix/iy+(int8_t)d)
        if ((hl_ix_iy > 0) && (lit == DA_LIT_hl))
        {
            if (!contents || (table < 4 && b == 0xe9))
            {
                lit = (DA_LIT_hl + hl_ix_iy);
            }
            else
            {
                offset = (int16_t)*(int8_t*)loc++;
                sprintf(buffer, "%s%c%02x", LiteralStringArray[DA_LIT_hl + hl_ix_iy], (offset < 0) ? '-' : '+', abs(offset));
                argText = &buffer[0];
                lit = 0xff; // switch lit out and argText in
            }
        }

        if (lit != 0xff)
        {
            argText = LiteralStringArray[lit];
        }

        if (contents)
        {
            sprintf(arg, "(%s)", argText);
        }
        else
        {
            strcpy(arg, argText);
        }

        argInfo >>= 4;
    }
    if (swap)
    {
        args[0] = arg1;
        args[1] = arg0;
    }
    if (argCount > 0)
    {
        strcat(line, " ");
        strcat(line, args[0]);
    }
    if (argCount > 1)
    {
        strcat(line, ", ");
        line = strcat(line, args[1]);
    }
    return loc;
}

Monday, October 19, 2015

Breakpoints Starting to Work

I'm finally getting breakpoints to start working!

RST 20H (0xe7) is what I use to generate breakpoints. Since the code copies itself to RAM banks and executes from RAM, the breakpoint code can swap in e7 for whatever instruction is at a certain point. This causes the execution to run off to 0x0020.

Once there, it pushes all registers. It also pushes sp (i.e. ld hl, 0; add hl, sp; push hl) and then calls a function that loads hl with a pointer to a function and jumps to it. The default function just returns. But if the pointer is modified, execution can go someplace else.

And that place is bpBreak.

The bpBreak function handles loading a pointer to the registers contents struct over the stack where the the registers are stored. That struct pointer can be used to display and change register contents. It then it replaces all the RST 20H opcodes with the original contents. While doing that, it updates the breakpoint ignore counter, deletes delete-on-break breakpoints, and deletes break-on-hit breakpoints if the pc is where the break occurred. Finally, if any breakpoint had a count of 0, it will start the break prompt, otherwise skip straight to continuing.

The break prompt is where user can interactively set, delete, and change breakpoints, view and change register contents, and view and change memory.

Step is done with temporary breakpoints that delete on break. A breakpoint usually goes at the beginning of the next instruction. The exceptions are ret, jp, and jr instructions since execution never gets beyond. Except, of course, when they are conditional. In that case, the breakpoint must be set at the destination of the jump. There are 6 possibilities of which I deal with 4;
call, jp: place breakpoint at *(uint16_t*)(bpRegs->pc+1)
jr: place breakpoint at pc+*(uint8_t*)(bpRegs->pc+1)
ret: place breakpoint at *(uint16_t*)(bpRegs->sp)
jp (hl): place breakpoint at bpRegs->hl
rst: this is not done but one could place breakpoints at 00, 08, 10, 18, 20, 28, 30, or 38
halt: this is not done but one could place a breakpoint at 66

Next is like step except if there is a call (i.e. breakpoint uses absolute addressing, may reach next instruction, and is not conditional), then the breakpoint is not placed at the destination. This fails to take into account that calls can be conditional, but the SDCC compiler is not generating conditional calls, at least at the optimization level I'm using.

Continue is a little tricky. It is assumed that the current instruction is a breakpoint. If it isn't this is overkill, but if it is, we can't just load the breakpoints and run--we'd break at the same point again. Rather, the breakpoint has be temporarily disabled, a step has to take place, and the breakpoint must be restored once past it. This is done by never loading any breakpoint at the current pc, and having the code that unloads the breakpoint determine if a break is necessary. If a deletion-break breakpoint is hit and has an ignore count greater than 0, it will not generate a break.

Here are two breakpoint files. The first is the basic breakpoint function:


// __asm__("rst 20h") == 0xe7
#define RST_20H ((uint8_t)(0xe7))

struct Regs* bpRegs;
struct BP bpInfo[BREAKPOINT_COUNT];


// declare fnRST_20H for telling bank 0 how to get to bpBreak
extern void(*fnRST_20H)();

// Initialize breakpoint system
void bpInit()
{
    memset(bpInfo, 0, sizeof(bpInfo));
    fnRST_20H = bpBreak;
}

// crt1 calls this instead of main. This calls main
void bpStart(void(*initBP)())
{
    bpSet((uint8_t*)initBP, (1 << BP_FLAG_ENABLED) | (1 << BP_FLAG_DELETE_ON_HIT), 0);
    bpLoadBreakPoints(NULL);
}

// Sets a breakpoint at location with behavior flags and an ignore count
uint8_t bpSet(uint8_t* location, uint8_t flags, uint8_t count)
{
    uint8_t bp;

    // get unusued and look for same
    for (bp = 0; bp < BREAKPOINT_COUNT; bp++)
    {
        struct BP* b = &bpInfo[bp];
        if ((b->flags & (1 << BP_FLAG_ASSIGNED)) == 0)
        {
            b->flags |= (1 << BP_FLAG_ASSIGNED);
            break;
        }
        else if (b->addr == location)
        {
            if (((b->flags ^ flags) & ~0x03) == 0)
            {
                // such a breakpoint already exists
                return bp;
            }
        }
    }

    // Set the breakpoint
    if (bp < BREAKPOINT_COUNT)
    {
        struct BP* b = &bpInfo[bp];
        b->flags = flags | (1 << BP_FLAG_ASSIGNED);
        b->addr = location;
        b->count = count;
        printf("Set BP%x @ %p (%x-%d)\r\n", bp, location, flags, count);
    }
    return bp;
}

// Delete a breakpoint
void bpUnset(uint8_t bp)
{
    struct BP* b = &bpInfo[bp];
    b->flags = 0x00;
    printf("Unset BP%x @ %p (%x-%d)\r\n", bp, b->addr, b->flags, b->count);
}

// Enable or disable a breakpoint
void bpEnable(uint8_t bp, bool enable)
{
    if (enable)
    {
        bpInfo[bp].flags |= (1 << BP_FLAG_ENABLED);
        bpInfo[bp].count = 0;
    }
    else
    {
        bpInfo[bp].flags &= ~(1 << BP_FLAG_ENABLED);
    }
}

// Find if a breakpoint is enabled
bool bpIsEnabled(uint8_t bp)
{
    return (bpInfo[bp].flags & (1 << BP_FLAG_ENABLED)) != 0;
}

// Gets the first breakpoint at addr of 0xff
uint8_t bpAt(uint8_t* addr)
{
    uint8_t bp;
    for (bp = 0; bp < BREAKPOINT_COUNT; bp++)
    {
        if (bpInfo[bp].addr == addr)
        {
            return bp;
        }
    }
    return 0xff;
}

// Swaps in the RST 20H into the locations with breakpoints
// Breakpoints are not swapped at 'pc'
void bpLoadBreakPoints(uint8_t* pc)
{
    uint8_t bp = 0;
    for (bp = 0; bp < BREAKPOINT_COUNT; bp++)
    {
        struct BP* b = &bpInfo[bp];
        if ((b->flags & 0x03) == 0x03 && b->addr != pc) // assigned and enabled and not at PC
        {
            uint8_t swap = *(b->addr);
            if (swap == RST_20H)
            {
                // already a breakpoint there
            }
            else
            {
                b->swap = swap;
                *(b->addr) = RST_20H;
                printf("RST 20 to %02x @ %p\r\n", b->swap, b->addr);
            }
        }
    }
}

// Restores RST 20H with the original opcodes
// decrements ignore count for breakpoints who addr is 'pc'
// Returns whether any breakpoints at 'pc' were hit and had an ignore count of 0
bool bpUnloadBreakPoints(uint8_t* pc)
{
    uint8_t bp = 0;
    bool doBreak = false;
    for (bp = 0; bp < BREAKPOINT_COUNT; bp++)
    {
        struct BP* b = &bpInfo[bp];
        if ((b->flags & (1 << BP_FLAG_ASSIGNED)) != 0)
        {
            if ((b->flags & (1 << BP_FLAG_ENABLED)) != 0)
            {
                uint8_t swap = *(b->addr);
                if (swap == RST_20H)
                {
                    printf("RST 20 from %02x @ %p\r\n", b->swap, b->addr);
                    *(b->addr) = b->swap;
                }
                if (b->addr == pc)
                {
                    if ((b->count > 0))
                    {
                        b->count--;
                    }
                    else
                    {
                        doBreak = true;
                    }

                    // delete on hit
                    if ((b->flags & (1 << BP_FLAG_DELETE_ON_HIT)) != 0)
                    {
                        b->flags = 0;
                    }
                }
            }
            // delete on break BP
            if ((b->flags & (1 << BP_FLAG_DELETE_ON_BREAK)) != 0)
            {
                b->flags = 0;
            }
        }
    }
    return doBreak;
}

// This set breakpoints for certain types: 0-step 1-next 2-continue
// Step/Next: if !BRANCH_NO_STEP set bp @ pc+il
// Step: if BRANCH_ABS set @ (pc+1)
// Step: if BRANCH_REL set @ pc + (pc+1)
// Step: if BRANCH_RET set @ (sp-2)
// Step: if BRANCH_HL set @ hl
void bpStep(uint8_t type)
{
    uint8_t* pc;
    uint8_t branchInfo;
    uint8_t instrLen;
    bool breakOnNextInstr;
    bool breakOnDest;
    uint8_t count;
    uint8_t nextFlags;

    // get PC
    pc = (uint8_t*)bpRegs->pc;

    // get next instruction address
    instrLen = z80InstructionLength(pc);
    branchInfo = instrLen & 0xf8;
    instrLen = instrLen & 0x07;

    printf("branch %02x\r\n", branchInfo);

    // figure out what to do
    // if not branching away permanently, set a breakpoint there
    breakOnNextInstr = (branchInfo & BRANCH_ALWAYS) == 0;

    // count is 0 unless type is continue
    // If count is 1, will not return to break point prompt--will just continue
    count = (type == 2) ? 1 : 0;

    // How flags work
    // step: delete on hit for next and delete on break dest
    // next: delete on hit for next
    // continue: always delete on any break--restart immediately
    nextFlags = (type == 2) ? (1 << BP_FLAG_DELETE_ON_BREAK) : (1 << BP_FLAG_DELETE_ON_HIT);
    nextFlags |= (1 << BP_FLAG_ENABLED);
    //nextFlags = (1 << BP_FLAG_DELETE_ON_BREAK) | (1 << BP_FLAG_ENABLED);

    // place breakpoint if not NO_STEP branch (jp **, jp (hl), ret/i/n, jr)
    if (breakOnNextInstr)
    {
        bpSet(pc + instrLen, nextFlags, count);
    }

    // BP on branch destination
    breakOnDest = ((branchInfo & 0xf8) != 0);
    if ((type == 1) && ((branchInfo & BRANCH_CALL) != 0))
    {
        breakOnDest = false; // if user is using 'next' on a call
    }

    if (breakOnDest)
    {
        uint8_t branchType = branchInfo & 0x30;
        uint16_t target = 0;
        // get branch addr (rel, abs, (hl), (sp-2)
        if (branchType == BRANCH_ABS)
        {
            target = *(uint16_t*)(pc + 1);
        }
        else if (branchType == BRANCH_REL)
        {
            target = (uint16_t)pc + (int8_t)(pc[1]);
        }
        else if (branchType == BRANCH_RET)
        {
            target = *(uint16_t*)(bpRegs->sp);
        }
        else// if (branchType == BRANCH_HL)
        {
            target = bpRegs->hl;
        }

        // place breakpoint
        bpSet((uint8_t*)target, (1 << BP_FLAG_DELETE_ON_BREAK) | (1 << BP_FLAG_ENABLED), count);
    }
}

// RST 20H calls this after 
void bpBreak() __naked
{
    //RST_20H all registers pushed
    __asm__("di");
    __asm__("ld hl, #2");
    __asm__("add hl, sp");
    __asm__("push hl");
    __asm__("ld (_bpRegs), hl");
    __asm__("call _bpPrompt");
    __asm__("pop hl ; toss");
    __asm__("ret");
}

This file snippet is for breakpoint prompt features:


struct BpCmd {
    const char* cmd;
    const char* desc;
    uint8_t(*fn)(const char* line);
};

void showMem(uint16_t address, uint16_t length)
{
    uint8_t i;
    char* ph;
    char* pa;
    char line[75];
    w_bb shift;
    address = address & 0xfff0;
    length = (length + 15) & 0xfff0;
    memset(line, ' ', sizeof(line) - 1);
    line[sizeof(line) - 1] = 0;
    line[4] = ':';
    while (length)
    {
        shift.uw = address;
        ph = &line[0];
        pa = &line[4 + 1 + 1 + 1 + 16 * 3 + 1];
        *ph++ = __ascii[shift.ubb.h >> 4];
        *ph++ = __ascii[shift.ubb.h & 0x0f];
        *ph++ = __ascii[shift.ubb.l >> 4];
        *ph++ = __ascii[shift.ubb.l & 0x0f];
        ph++;
        ph++;
        // 16 bytes
        for (i = 0; i < 16; i++)
        {
            uint8_t b;
            b = *(char*)(address + i);
            *ph++ = __ascii[b >> 4];
            *ph++ = __ascii[b & 0x0f];
            ph++;

            // unprintables to .
            if (b < ' ' || b > 0x7e)
            {
                b = '.';
            }
            *pa++ = b;

            // gap
            if (i == 7)
            {
                ph++;
                pa++;
            }

        }
        puts(line);
        address += 16;
        length -= 16;
    }
}

static void showRegs()
{
    printf("af: %04x  bc: %04x  de: %04x  hl=%04x\r\n", bpRegs->af, bpRegs->bc, bpRegs->de, bpRegs->hl);
    printf("pc: %04x  sp: %04x  ix: %04x  iy=%04x\r\n", bpRegs->pc, bpRegs->sp, bpRegs->ix, bpRegs->iy);
}

static void showBreakpoint(uint8_t bp)
{
    struct BP* b = &bpInfo[bp];
    printf("%x: addr=%04x en=%x count=%02x\r\n", bp, b->addr, (b->flags & (1 << BP_FLAG_ENABLED)) != 0, b->count);
}
static void showBreakpoints()
{
    uint8_t i;
    for (i = 0; i < BREAKPOINT_COUNT; i++)
    {
        struct BP* b = &bpInfo[i];
        if ((b->flags & (1 << BP_FLAG_ASSIGNED)) != 0)
        {
            showBreakpoint(i);
        }
    }
}

static const struct BpCmd commands[] =
{
    {
        "?",
        "list",
        bpIntHelp
    },
    {
        "l",
        "list bp's",
        bpIntList
    },
    {
        "s",
        "step",
        bpIntStep
    },
    {
        "n",
        "next",
        bpIntNext
    },
    {
        "c",
        "continue",
        bpIntContinue
    },
    {
        "b",
        "set bp @ addr",
        bpIntCreate
    },
    {
        "d",
        "delete bp # or all",
        bpIntDelete
    },
    {
        "t",
        "toggle bp",
        bpIntToggle
    },
    {
        "r",
        "see regs or set rr v",
        bpIntRegs
    },
    {
        "m",
        "see RAM",
        bpIntMem
    },
};

const static uint8_t commandLength = sizeof(commands) / sizeof(commands[0]);


static uint8_t bpIntList(const char* line)
{
    line; // suppress warning
    showBreakpoints();
    return 0;
}

static uint8_t bpIntHelp(const char* line)
{
    uint8_t i;
    line; // suppress warning
    for (i = 0; i < commandLength; i++)
    {
        printf("%s - %s\r\n", commands[i].cmd, commands[i].desc);
    }
    return 0;
}

static uint8_t bpIntContinue(const char* line)
{
    line; // suppress warning
    bpStep(2);
    return 0xff; // continue
}

static uint8_t bpIntStep(const char* line)
{
    line; // suppress warning
    bpStep(0);
    return 0xff; // continue
}

static uint8_t bpIntNext(const char* line)
{
    line; // suppress warning
    bpStep(1);
    return 0xff; // continue
}

static uint8_t bpIntCreate(const char* line)
{
    if (*line++ == ' ')
    {
        uint16_t addr = extractHex(&line);
        uint8_t count = 0;
        if (*line == ' ')
        {
            line++;
            count = extractHex(&line);
        }
        if (*line == 0)
        {
            uint8_t i = bpSet((uint8_t*)addr, (1<<BP_FLAG_ENABLED), count);
            if (i == 0xff)
            {
                puts("BP full");
                return 1;
            }
            else
            {
                showBreakpoint(i);
            }
        }
        else
        {
            return 2;
        }
    }
    else
    {
        return 3;
    }
    return 0;
}

static uint8_t bpIntDelete(const char* line)
{
    uint8_t status = 0;
    if (*line == ' ')
    {
        uint8_t i;
        line++;
        i = extractHex(&line);
        if (*line == 0 && i < BREAKPOINT_COUNT)
        {
            bpUnset(i);
        }
    }
    return 0;
}

static uint8_t bpIntToggle(const char* line)
{
    //uint8_t status = 0;
    uint8_t bp;
    if (*line == ' ')
    {
        bp = extractHex(&line);
    }
    else if (*line == 0)
    {
        bp = bpAt((uint8_t*)bpRegs->pc);
    }
    else
    {
        return 1;
    }
    if (bp >= BREAKPOINT_COUNT)
    {
        return 2;
    }
    bpEnable(bp, !bpIsEnabled(bp));
    showBreakpoint(bp);
    return 0;
}

static const char* const regs = "SPiyixhldebcafpc";

static uint8_t bpIntRegs(const char* line)
{
    //uint8_t status = 0;
    if (*line == 0)
    {
        showRegs();
        return 0;
    }
    else if (*line == ' ')
    {
        uint8_t i;
        uint16_t search;
        const uint16_t* reg = (const uint16_t*)regs;

        line++;
        search = *(uint16_t*)line;

        for (i = 0; i < 7; i++)
        {
            if (search == *reg)
            {
                uint16_t val;
                line += 3;
                val = extractHex(&line);
                if (*line == 0)
                {
                    ((uint16_t*)bpRegs)[i] = val;
                    showRegs();
                }
                else
                {
                    puts("?");
                }
            }
            reg++;
        }
    }
    return 0;
}

static uint8_t bpIntMem(const char* line)
{
    uint16_t addr = 0;
    uint16_t len = 16;
    if (*line++ == ' ')
    {
        addr = extractHex(&line);
        if (*line++ == ' ')
        {
            len = extractHex(&line);
        }
        if (*line == 0)
        {
            showMem(addr, len);
            return 0;
        }
    }
    return 1;
}

// do prompt for breaks
//  ?           list commands
//  l           list breakpoints
//  c           continue
//  s           step
//  n           next
//  b xxxx (cc) break @ xxxx (optional countdown)
//  d ##        delete ##
//  t ##        toggle ## enable
//  r           show regs
//  r rr xxxx   set reg (except sp)
//  m xxxx xxxx show mem
void bpPrompt()
{
    char buffer[32];
    char* p;
    uint8_t i;
    uint16_t pc = bpRegs->pc - 1; // source of RST_20
    uint8_t interruptSave;
    bool doBreak;

    bpRegs->pc = pc; // restore to the instruction that was supposed to be there
    doBreak = bpUnloadBreakPoints((uint8_t*)pc);
    interruptSave = ei_save();

    while (doBreak)
    {
        uint8_t result = 0;
        char c;
        printf("(brk @ %p)> ", pc);
        gets(buffer);
        p = &buffer[0];
        c = *p;
        for (i = 0; i < commandLength; i++)
        {
            const char* cmd = commands[i].cmd;
            uint8_t len = strlen(cmd);
            if (strncmp(commands[i].cmd, p, len) == 0)
            {
                result = commands[i].fn(&p[len]);
            }
        }

        if (result == 0xff)
        {
            break;
        }
        else if (result != 0)
        {
            printf("error %d\r\n", result);
        }
    }
    bpLoadBreakPoints((uint8_t*)pc);
    di_restore(interruptSave);
}


This is the part of interrupt.c that is relevant:


static void ret() __naked
{
    __asm__("ret");
}
// This is preset to 'ret' If the code runs frojmj flash, this is necessary.
// If the code runs from RAM, this can be overridden.
void(*fnRST_20H)() = ret;
static void call_hl() __naked
{
    __asm__("jp (hl)");
}
void do_RST_20H() __naked
{
    __asm__("push af");
    __asm__("push bc");
    __asm__("push de");
    __asm__("push hl");
    __asm__("push ix");
    __asm__("push iy");
    // push sp
    __asm__("ld hl, #14");
    __asm__("add hl, sp");
    __asm__("push hl");

    __asm__("ld hl, (_fnRST_20H)");
    __asm__("call _call_hl");

    __asm__("pop iy"); //  toss  SP
    __asm__("pop iy");
    __asm__("pop ix");
    __asm__("pop hl");
    __asm__("pop de");
    __asm__("pop bc");
    __asm__("pop af");
    __asm__("ret");
}

Z-80 Instruction Lengths

In order to handle breakpoints functionality of step, next, and continue, it is necessary to know 1) how long the current instruction is and 2) whether and how it branches.

So I coded up a quick function that takes a pointer to a location and returns 1) 1-4 in the lower 3 bits with instruction length, 2 bits with the branch type (absolute, relative, return, and jp (hl)), one bit to indicate the branch is conditional, and one bit for instructions that always branch away and will never get to the next instruction.

Note that the 'rst' and 'halt' commands don't have branch information. Anyone wanting to try to use this code will want to add that if they need it.

Here it is:


// The following are found in pBranchInfo
#define BRANCH_CALL     (0x08)
// relative to pc + (int8_t)(opcode+1) ALWAYS or COND
#define BRANCH_REL      (0x20)
// absolute to (void*)(opcode+1) ALWAYS or COND
#define BRANCH_ABS      (0x10)
// return to (void*)(sp-2) ALWAYS or COND
#define BRANCH_RET      (0x30)
// to (hl) // ALWAYS but never COND
#define BRANCH_HL       (0x00)
// conditional--bp necessary after instruction
#define BRANCH_COND     (0x40)
// always--will never got to pc+length--certain to branch
#define BRANCH_ALWAYS   (0x80)


struct InstLen
{
    uint8_t mask;
    uint8_t value;
    uint8_t data;
};

static const struct InstLen scan00[] =
{
    // ld rr, **
    {
        0xcf, 0x01, 3
    },
    // ld (hl)/a <-> **
    {
        0xe7, 0x22, 3
    },
    // ld r, *
    {
        0xc7, 0x06, 2
    },
    // jr f, *
    {
        0xe7, 0x20, 2 | BRANCH_REL | BRANCH_COND
    },
    // djnz *
    {
        0xff, 0x10, 2 | BRANCH_REL | BRANCH_COND
    },
    // jr *
    {
        0xff, 0x18, 2 | BRANCH_REL | BRANCH_ALWAYS
    },
};

static const struct InstLen scan11[] =
{
    // ret f (not detault b/c branch)
    {
        0xc7, 0xc0, 1 | BRANCH_RET | BRANCH_COND
    },
    // ret (not detault b/c branch)
    {
        0xff, 0xc9, 1 | BRANCH_RET | BRANCH_ALWAYS
    },
    // jp (hl)
    {
        0xff, 0xe9, 1 | BRANCH_HL | BRANCH_ALWAYS
    },
    // jp f, **
    {
        0xc7, 0xc2, 3 | BRANCH_ABS | BRANCH_COND
    },
    // call f, **
    {
        0xc7, 0xc4, 3 | BRANCH_ABS | BRANCH_COND | BRANCH_CALL
    },
    // jp **
    {
        0xff, 0xc3, 3 | BRANCH_ABS | BRANCH_ALWAYS
    },
    // call **
    {
        0xff, 0xcd, 3 | BRANCH_ABS | BRANCH_CALL
    },
    // arith *, *
    {
        0xc7, 0xc6, 2
    },
    // out/in(*), a
    {
        0xf7, 0xd3, 2
    },
    // bit op
    {
        0xff, 0xcb, 2
    }
};

static const struct InstLen scanED[] =
{
    // ld (rr) <-> **
    {
        0xc7, 0x43, 4
    }
};

static const struct InstLen scanDDFD[] =
{
    // ld ix, **
    {
        0xff, 0x21, 4
    },
    // ld ix <-> (**)
    {
        0xf7, 0x22, 4
    },
    // ld (ix+*), * order dep
    {
        0xff, 0x36, 4
    },
    // various
    {
        0x07, 0x06, 3
    },
    // ld (ix+*), r
    {
        0xb8, 0x30, 3
    },
    // bit ops
    {
        0xff, 0xcb, 3
    },
};

// Returns the length in the first 3 bits and the BRANCH_* bits in the upper 5 bits
uint8_t z80InstructionLength(uint8_t* start)
{
    uint8_t opcode = *start++;
    const struct InstLen* scanner;
    int i;
    uint8_t length;
    uint8_t instructionLength = 1;

    if ((opcode & 0x80) == 0)
    {
        if ((opcode & 0x40) == 0)
        {
            scanner = scan00;
            length = sizeof(scan00) / sizeof(scan00[0]);
        }
        else
        {
            return 1;
        }
    }
    else
    {
        if ((opcode & 0x40) == 0)
        {
            return 1;
        }
        else
        {
            if ((opcode & 0xdf) == 0xdd)
            {
                scanner = scanDDFD;
                length = sizeof(scanDDFD) / sizeof(scanDDFD[0]);
                instructionLength = 2;
                opcode = *start++;
            }
            else if (opcode == 0xed)
            {
                scanner = scanED;
                length = sizeof(scanED) / sizeof(scanED[0]);
                instructionLength = 2;
                opcode = *start++;
            }
            else
            {
                scanner = scan11;
                length = sizeof(scan11) / sizeof(scan11[0]);
            }
        }
    }
    for (i = 0; i < length; i++)
    {
        const struct InstLen* scan = &scanner[i];
        if ((scan->mask & opcode) == scan->value)
        {
            instructionLength = scan->data;
            break;
        }
    }
    return instructionLength;
}

Friday, October 9, 2015

Update

I've been working on testing the vector which allows me to have library (C/driver) functions in bank 0 and separately compile a program in bank 1 that links to a vector that just forwards the calls from bank 1 to the right place in bank 0. I've found a few issues but for the most part it works as expected.

Also, I have coded but not yet started testing the breakpoint code. All it does now is create/delete/enable/disable breakpoints, show/set registers, and dump memory. Step and next require getting the size of the current instruction as well as the possible branch destination. So that's still on the back burner.

Well, off to start testing the debug code....

Saturday, October 3, 2015

Breakpoints

Debugging is hard. It would easier with breakpoints. But breakpoints need to be implemented. How?

Well, I can use a RST vector. Say RST 20H.

If I'm running from RAM, I can replace the first byte of all instructions at a break location with RST 20H and then start executing. Then when that's called, go into breakpoint mode. In breakpoint mode, I need to make the following commands available:
set break point (addr)
reset break point (bp #)
run
write register (including sp and pc)
dump registers
dump mem (addr len)

I should also have support in the UART to go into breakpoint mode from inside getchar().

Step is just where you put a breakpoint on either the next assembly instruction or the first assembly instruction of the next C line, then erase it when done. In the case of conditional branches and returns, it's a little tricker because they require either 2 breakpoints or evaluation of the condition flag to know where to put the next instruction. Also jp (hl) needs to be managed.

I just have to figure out how to use sdgdb..... Either that or make my own source debugger....


Thursday, October 1, 2015

Progress

It took me a little while to figure out why my bank wasn't running. __asm__("call 4030") does not output machine code that jumps to 0x4030--it jumps to 4030 or 0x0fbe. That's wrong. So I added in the "0x" and all was well.

So now I am starting to wrap up a refactoring exercise.

I added a block that will go in all 16 sections. Each will have a pointer at 0x0026 that will point to this block.

#define BANKINFO_TYPE_UNKNOWN ((uint8_t)0x00)
#define BANKINFO_TYPE_CVECTOR ((uint8_t)0x01)
#define BANKINFO_TYPE_APPLET ((uint8_t)0x02)
#define BANKINFO_TYPE_EXECUTABLE ((uint8_t)0x03)

// Every bank has a pointer at 0026 that points to a const BankInfo* _BANK_INFO; both crt0.s and crt1.s
// expect a _BANK_INFO to be defined. This will typically be where main is defined.

typedef struct BankInfo {
    uint16_t CVersion; // 0{ix} 1(ix)
    const char* Name; // 2{ix} 3(ix)
    const char* TimeStamp; // 4{ix} 5(ix)
    uint8_t Flags; // 6{ix}
    uint8_t Type; // 7(ix) 1=c vector, 2=applet, 3=bank
    uint16_t W0102; // 8{ix} 9(ix) // valid only is this is 0102
    uint16_t W0304; // 10{ix} 11(ix)
} BankInfo;

This will be used for listing the contents of the flash. There will be 3 types of flash banks:

  • C Vector: These have the C library and drivers that the applets can use. But they don't really need anything else.
  • Applet: These will run the programs that I want to run
  • Executable: These will run standalone programs or menu items.


The applets require that they are linked with crt1.rel. The others with crt0.s. The C vectors need to link in c_vector.c.

At this point, I have to start reserving certain banks:
00: flash bank 0 is always run from reset. It will have a standalone executable with complex menu, but no c vector
01: flash bank 1 is always in bank 1 from reset. It will always be available for bank 00 to be able to hold programs larger than 16K.
10: RAM bank 10: This will be placed in bank 2 (i.e. 0x8000-0xbfff). It is there by default
11: RAM bank 11: This will be placed in bank 2 (i.e. 0xc000-0xffff). It is there by default
12: RAM bank 12: RAM bank 12 will be the standard bank for run from RAM in bank 0 (i.e. 0x0000-0x3fff)
13: RAM bank 13: RAM bank 13 will be the standard bank for run from RAM in bank 1 (i.e. 0x4000-0x7fff)
14: RAM bank 14: will be used for the standard bank that holds the C vector (0x0000-0x3fff)

I had to deal with the fact that the BankInfo block may be placed in blocks other than the block it expects, so the pointers to strings also have to point to the proper location.

All this bank switching has my brain running in circles, but it's finally getting into shape.

Tuesday, September 22, 2015

Calling C From Bank 1

One of my goals is to be able to separately compile a program that runs in banks 1 and 2 while the system manages from banks 0 and 3. Since I don't want to have duplicates of the c library, I need a way to let a the compiler use the c library and driver functions that are in bank 0. So this morning, I coded up a system to do that.

I added a crt1.s which is the crt start up for bank 1. It doesn't need to deal with interrupts, and some basic start up code, for example. It just needs to prepare the stack pointer, deal with it's C initialization, and then start main. At the end of main, it has to return to the caller in bank 0.

I haven't tested any of this, but the basic plan is to have a vector of jumps at the top of bank 0. A version will sit at 0x3f11. The vector is jumps to functions in bank 0.

For example. If I want to call printf from the compiled code for bank 1, it will link the vector which will have a:
_printf::
 jp 0
compiled into a location somewhere in the 3f11-3ffd range. Specifically at 0x4000 - sizeof(struct c_vector) + offsetof(struct c_vector, printf).

Because of the way the intel hex programmer works, this will not be burnt into the flash because it is outside the 0x4000-0x7fff range. So a call to printf will go to that location where the jp 0 is. But there won't be a jp 0 there. What will be there is a jp _printf placed there by the compiler that compiled the bank 0 code. The code compiled for bank 1 will, as a result, call the printf compiled separately for bank 0. Of course, the struct c_vector and the jp's have to align perfectly.

If that description was a little complicated, have a look at the code (recall it's not tested yet). It's in progress.

cvector.h:

#ifndef INCLUDE_CVECTORS_H
#define INCLUDE_CVECTORS_H

#ifndef _SDCC_MALLOC_TYPE_MLH
#define _SDCC_MALLOC_TYPE_MLH
#endif

#ifndef __SDCC_BROKEN_STRING_FUNCTIONS
#define __SDCC_BROKEN_STRING_FUNCTIONS
#endif

#ifndef __SDCC_z80
#define __SDCC_z80
#endif

#ifndef _Bool
#define _Bool unsigned char
#endif

#include <stdint.h>
#include <stdbool.h>
#include <stdarg.h>
#include "ringBuffer.h"


struct c_vector
{
    // c_ext
    uint8_t jp4e;
    void (*gets_length)(int maxlen);
    uint8_t jp4d;
    void(*gets_echoSuppress)(bool suppressEcho);

    // idle
    uint8_t jp4c;
    void(*idle)(void(*pIdleFn)(void* p), void* p);

    // interrupt
    uint8_t jp4b;
    void(*di)();
    uint8_t jp4a;
    void(*ei)();

    // gpio
    uint8_t jp49;
    uint8_t(*gpioReadDirection)();
    uint8_t jp48;
    void(*gpioWriteDirection)(uint8_t direction, uint8_t mask);
    uint8_t jp447;
    uint8_t(*gpioReadOutLevel)();
    uint8_t jp46;
    void(*gpioWriteLevel)(uint8_t out, uint8_t mask);
    uint8_t jp45;
    void(*gpioWriteBitLevel)(uint8_t bit, bool level);
    uint8_t jp44;
    uint8_t(*gpioReadInLevel)();

    // spi
    uint8_t jp43;
    uint8_t(*spiExchange)(uint8_t b, uint8_t flags, uint8_t count);

    // tick
    uint8_t jp42;
    uint32_t(*getTicks)();

    // timer
    uint8_t jp41;
    void(*timerInit)();
    uint8_t jp40;
    int(*timerGetAvailableTimerId)();
    uint8_t jp3f;
    void(*timerFree)(int timerId);
    uint8_t jp3e;
    void(*timerSet)(int timerId, uint32_t interval, bool repeat, bool start, void(*cb)(int timer));
    uint8_t jp3d;
    void(*timerGet)(int timerId, uint32_t* pTrigger, uint32_t* pInterval, bool* pRepeat, bool* pRunning, void(**pCb)(int timer));
    uint8_t jp3c;
    void(*timerStop)(int timerId);
    uint8_t jp3b;
    void(*timerRun)(int timerId);
    uint8_t jp3a;
    void(*timerResetInterval)(int timerId);

    // uart
    uint8_t jp39;
    bool(*uartConfig)(uint8_t configLow);
    uint8_t jp38;
    void(*uartSupressRTS)(bool always, bool exceptRecv);
    uint8_t jp37;
    uint8_t(*uartErrorReadAndClear)(uint8_t clearMask);
    uint8_t jp36;
    RB_INT_TYPE(*send)(uint8_t* pByte, RB_INT_TYPE count);
    uint8_t jp35;
    RB_INT_TYPE(*recv)(uint8_t* pByte, RB_INT_TYPE count);
    uint8_t jp34;
    void(*pend)(void(*pIdleFn)(void* p), void* p);

    // stdio
    uint8_t jp33;
    char(*getchar)();
    uint8_t jp32;
    void(*putchar)(char c);
    uint8_t jp31;
    int(*puts)(const char* s);
    uint8_t jp30;
    char* (*gets)(char* s);
    uint8_t jp2f;
    int(*printf)(char* format, ...);
    uint8_t jp2e;
    int(*vprintf)(char* format, va_list ap);
    uint8_t jp2d;
    int(*sprintf)(char* dest, char* format, ...);
    uint8_t jp2c;
    int(*vsprintf)(char* dest, char* format, va_list ap);

    // stdlib
    uint8_t jp2b;
    void* (*malloc)(size_t size);
    uint8_t jp2a;
    void* (*calloc)(size_t size, size_t count);
    uint8_t jp29;
    void* (*realloc)(void* old, size_t newSize);
    uint8_t jp28;
    void(*free)(void* m);


    // string
    uint8_t jp27;
    void* (*memcpy)(void* dest, const void* src, size_t n);
    uint8_t jp26;
    void* (*memmove)(void *dest, const void *src, size_t n);
    uint8_t jp25;
    char* (*strcpy)(char* dest, const char* src);
    uint8_t jp24;
    char* (*strncpy)(char* dest, const char* src, size_t n);
    uint8_t jp23;
    char* (*strcat)(char* dest, const char* src);
    uint8_t jp22;
    char* (*strncat)(char* dest, const char* src, size_t n);
    uint8_t jp21;
    int(*memcmp)(const void* s1, const void* s2, size_t n);
    uint8_t jp20;
    int(*strcmp)(const char* s1, const char* s2);
    uint8_t jp1f;
    int(*strncmp)(const char* s1, const char* s2, size_t n);
    uint8_t jp1e;
    size_t(*strxfrm)(char *dest, const char* src, size_t n);
    uint8_t jp1d;
    void* (*memchr)(const void *s, int c, size_t n);
    uint8_t jp1c;
    char* (*strchr)(const char *s, char c); /* c should be int according to standard. */
    uint8_t jp1b;
    size_t(*strcspn)(const char *s, const char *reject);
    uint8_t jp1a;
    char* (*strpbrk)(const char *s, const char *accept);
    uint8_t jp19;
    char* (*strrchr)(const char *s, char c); /* c should be int according to standard. */
    uint8_t jp18;
    size_t(*strspn)(const char *s, const char *accept);
    uint8_t jp17;
    char* (*strstr)(const char* haystack, const char *needle);
    uint8_t jp16;
    char* (*strtok)(char* str, const char * delim);
    uint8_t jp15;
    void* (*memset)(void *s, unsigned char c, size_t n); /* c should be int according to standard. */
    uint8_t jp14;
    size_t(*strlen)(const char *s);

    // stdlib
    uint8_t jp13;
    int(*atoi)(const char* s);
    uint8_t jp12;
    long(*atol)(const char* s);
    uint8_t jp11;
    int(*rand)();
    uint8_t jp10;
    void(*srand)(unsigned int seed);
    uint8_t jp0f;
    int(*abs)(int i);
    uint8_t jp0e;
    long(*labs)(long i);

    // ctype
    uint8_t jp0d;
    int(*isblank)(int c);
    uint8_t jp0c;
    int(*isdigit)(int c);
    uint8_t jp0b;
    int(*islower)(int c);
    uint8_t jp0a;
    int(*isupper)(int c);
    uint8_t jp09;
    int(*isalnum)(int c);
    uint8_t jp08;
    int(*isalpha)(int c);
    uint8_t jp07;
    int(*iscntrl)(int c);
    uint8_t jp06;
    int(*isgraph)(int c);
    uint8_t jp05;
    int(*isprint)(int c);
    uint8_t jp04;
    int(*ispunct)(int c);
    uint8_t jp03;
    int(*isspace)(int c);
    uint8_t jp02;
    int(*isxdigit)(int c);
    uint8_t jp01;
    int(*tolower)(int c);
    uint8_t jp00;
    int(*toupper)(int c);

    // version --corresponds to 0x3ffe
    uint16_t _cVectorVersion;
};

#endif



c_vector.c:

#include "c_vector.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <malloc.h>
#include "menuDriver.h"
#include "c_ext.h"
#include "interrupt.h"
#include "gpio.h"
#include "spi.h"
#include "tick.h"
#include "idle.h"
#include "timer.h"
#include "uart.h"
#include "diag.h"

#ifndef __SDCC
#define __at(x)
#endif

void* proxy_memcpy(void* dest, const void* src, size_t n)
{
    return memcpy(dest, src, n);
}

void* proxy_memmove(void *dest, const void *src, size_t n)
{
    return memmove(dest, src, n);
}

char* proxy_strcpy(char* dest, const char* src)
{
    return strcpy(dest, src);
}

char* proxy_strncpy(char* dest, const char* src, size_t n)
{
    return strncpy(dest, src, n);

}

char* proxy_strchr(const char *s, char c)
{
    return strchr(s, c);
}

void* proxy_memset(void *s, unsigned char c, size_t n)
{
    return memset(s, c, n);
}
#define JP_OPCODE (uint8_t)(0xc3)

const struct c_vector __at(0x4000 - sizeof(struct c_vector)) C_VECTOR = {
 JP_OPCODE,
    gets_length,
    JP_OPCODE,
    gets_echoSuppress,

    JP_OPCODE,
    idle,

    JP_OPCODE,
    di,
    JP_OPCODE,
    ei,

    JP_OPCODE,
    gpioReadDirection,
    JP_OPCODE,
    gpioWriteDirection,
    JP_OPCODE,
    gpioReadOutLevel,
    JP_OPCODE,
    gpioWriteLevel,
    JP_OPCODE,
    gpioWriteBitLevel,
    JP_OPCODE,
    gpioReadInLevel,

    JP_OPCODE,
    spiExchange,

    JP_OPCODE,
    getTicks,

    JP_OPCODE,
    timerInit,
    JP_OPCODE,
    timerGetAvailableTimerId,
    JP_OPCODE,
    timerFree,
    JP_OPCODE,
    timerSet,
    JP_OPCODE,
    timerGet,
    JP_OPCODE,
    timerStop,
    JP_OPCODE,
    timerRun,
    JP_OPCODE,
    timerResetInterval,

    JP_OPCODE,
    uartConfig,
    JP_OPCODE,
    uartSupressRTS,
    JP_OPCODE,
    uartErrorReadAndClear,
    JP_OPCODE,
    send,
    JP_OPCODE,
    recv,
    JP_OPCODE,
    pend,

    JP_OPCODE,
    getchar,
    JP_OPCODE,
    putchar,
    JP_OPCODE,
    puts,
    JP_OPCODE,
    gets,
    JP_OPCODE,
    printf,
    JP_OPCODE,
    vprintf,
    JP_OPCODE,
    sprintf,
    JP_OPCODE,
    vsprintf,

    JP_OPCODE,
    malloc,
    JP_OPCODE,
    calloc,
    JP_OPCODE,
    realloc,
    JP_OPCODE,
    free,

    JP_OPCODE,
    proxy_memcpy,
    JP_OPCODE,
    proxy_memmove,
    JP_OPCODE,
    proxy_strcpy,
    JP_OPCODE,
    proxy_strncpy,
    JP_OPCODE,
    strcat,
    JP_OPCODE,
    strncat,
    JP_OPCODE,
    memcmp,
    JP_OPCODE,
    strcmp,
    JP_OPCODE,
    strncmp,
    JP_OPCODE,
    strxfrm,
    JP_OPCODE,
    memchr,
    JP_OPCODE,
    proxy_strchr,
    JP_OPCODE,
    strcspn,
    JP_OPCODE,
    strpbrk,
    JP_OPCODE,
    strrchr,
    JP_OPCODE,
    strspn,
    JP_OPCODE,
    strstr,
    JP_OPCODE,
    strtok,
    JP_OPCODE,
    proxy_memset,
    JP_OPCODE,
    strlen,

    JP_OPCODE,
    atoi,
    JP_OPCODE,
    atol,
    JP_OPCODE,
    rand,
    JP_OPCODE,
    srand,
    JP_OPCODE,
    abs,
    JP_OPCODE,
    labs,

    JP_OPCODE,
    isblank,
    JP_OPCODE,
    isdigit,
    JP_OPCODE,
    islower,
    JP_OPCODE,
    isupper,
    JP_OPCODE,
    isalnum,
    JP_OPCODE,
    isalpha,
    JP_OPCODE,
    iscntrl,
    JP_OPCODE,
    isgraph,
    JP_OPCODE,
    isprint,
    JP_OPCODE,
    ispunct,
    JP_OPCODE,
    isspace,
    JP_OPCODE,
    isxdigit,
    JP_OPCODE,
    tolower,
    JP_OPCODE,
    toupper,

    0x0101
};

const struct MenuItem menuItems[] =
{
 // Banking

 // Flash

 // Running

    {
 NULL,
 NULL
    }
};

void main()
{
    ShowByte(0x00);
    ShowByte(0xff);

    diagInit();
    DiagDir(1);
    tickInit();
    uartInit();
    gpioWriteLevel(0x00, 0x08);
    gpioWriteDirection(0x08, 0x08);
    uartSupressRTS(false, true);

    while (!uartConfig(0 | (uint8_t)UART_BAUD_9600))
    {
        // do nothing
    }

    ei();

    runMenu();
}

crt1.s:

;;

 .module crt1

 .globl _main

 .area _HEADER (ABS)
; This next section must remain in sync with cvectors.h
; This will be part of the intelhex file but will be ignored by the intel hex programmer
; because it is out of the range

; inspect crt1.rel and ensure _cVectorVersion looks like this:
; S _cVectorVersion Def3FFE
; if it is not, adjust this .org accordingly and update _cVectorVersion to reflect a new C vector
 .org 0x3f11
_gets_length::
 jp 0
_gets_echoSuppress::
 jp 0
_idle::
 jp 0
_di::
 jp 0
_e::
 jp 0
_gpioReadDirection::
 jp 0
_gpioWriteDirection::
 jp 0
_gpioReadOutLevel::
 jp 0
_gpioWriteLevel::
 jp 0
_gpioWriteBitLevel::
 jp 0
_gpioReadInLevel::
 jp 0
_spiExchange::
 jp 0
_getTicks::
 jp 0
_timerInit::
 jp 0
_timerGetAvailableTimerId::
 jp 0
_timerFree::
 jp 0
_timerSet::
 jp 0
_timerGet::
 jp 0
_timerStop::
 jp 0
_timerRun::
 jp 0
_timerResetInterval::
 jp 0
_uartConfig::
 jp 0
_uartSupressRTS::
 jp 0
_uartErrorReadAndClear::
 jp 0
_send::
 jp 0
_recv::
 jp 0
_pend::
 jp 0
_getchar::
 jp 0
_putchar::
 jp 0
_puts::
 jp 0
_gets::
 jp 0
_printf::
 jp 0
_vprintf::
 jp 0
_sprintf::
 jp 0
_vsprintf::
 jp 0
_malloc::
 jp 0
_calloc::
 jp 0
_realloc::
 jp 0
_free::
 jp 0
_memcpy::
 jp 0
_memmove::
 jp 0
_strcpy::
 jp 0
_strncpy::
 jp 0
_strcat::
 jp 0
_strncat::
 jp 0
_memcmp::
 jp 0
_strcmp::
 jp 0
_strncmp::
 jp 0
_strxfrm::
 jp 0
_memchr::
 jp 0
_strchr::
 jp 0
_strcspn::
 jp 0
_strpbrk::
 jp 0
_strrchr::
 jp 0
_strspn::
 jp 0
_strstr::
 jp 0
_strtok::
 jp 0
_memset::
 jp 0
_strlen::
 jp 0
_atoi::
 jp 0
_atol::
 jp 0
_rand::
 jp 0
_randSeed::
 jp 0
_abs::
 jp 0
_labs::
 jp 0
_isblank::
 jp 0
_isdigit::
 jp 0
_islower::
 jp 0
_isupper::
 jp 0
_isalnum::
 jp 0
_isalpha::
 jp 0
_iscntrl::
 jp 0
_isgraph::
 jp 0
_isprint::
 jp 0
_ispunct::
 jp 0
_isspace::
 jp 0
_isxdigit::
 jp 0
_tolower::
 jp 0
_toupper::
 jp 0
_cVectorVersion::
 .dw 0x0101

 ; 4000-4020 unused for now since intel Hex might overlap 4000

 ;; required c vector version the applet expects
 ;; C program must provide a const uint16_t  CVECTOR_VERSION = 0x0101; // adjust accordingly
 .globl _CVECTOR_VERSION

 .org 0x4020
 .dw _CVECTOR_VERSION
 
 ;; pointer to name of the applet
 ;; C program must provide a const char* const APPLET_NAME = "applet name here";
 .globl _APPLET_NAME
 .org 0x4022
 .dw _APPLET_NAME

 ;; pointer to name of the applet
 ;; C program must provide a const char* const APPLET_NAME = "applet name here";
 .globl _APPLET_TIMESTAMP
 .org 0x4024
 .dw _APPLET_TIMESTAMP

 ;; applet indicator--is these aren't 1,2,3,4, then the flash block is not an applet
 .org 0x4026
 .dw 0x0102
 .dw 0x0304

 .org 0x4030
init:
 ;; Set stack pointer directly above top of memory.
 ld hl, #0
 add hl, sp
 ld sp,#0xc000
 ;; Create a space to be used by bank switcher
 push hl

 ;; store the SP from the code that called this
 push hl

 ;; Initialise global variables
 call gsinit

 call _main

 ; return to code that called us
 pop hl
 ld sp, hl

 ret

 ;; Ordering of segments for the linker.
 .area _HOME
 .area _CODE
 .area _INITIALIZER
 .area   _GSINIT
 .area   _GSFINAL

 .area _DATA
 .area _INITIALIZED
 .area _BSEG
 .area   _BSS
 .area   _HEAP

 .area   _CODE

 .area   _GSINIT
gsinit::
 ld bc, #l__INITIALIZER
 ld a, b
 or a, c
 jr Z, gsinit_next
 ld de, #s__INITIALIZED
 ld hl, #s__INITIALIZER
 ldir
gsinit_next:

 .area   _GSFINAL
 ret


Here is how it's used. I define APPLET_NAME, APPLET_TIMESTAMP, and CVECTOR_VERSION, and main(). Next it is liniked with a Makefile that links with crt1 instead of crt0 and that uses --code-seg and --data-seg of 0x4020 and 0x8000.

Here is an example that I'm working toward.
clock.c:

#include <stdio.h>
#include "uart.h"
#include "timer.h"

const char* const APPLET_NAME = "clock";
const char* const APPLET_TIMESTAMP = __TIMESTAMP__;
const uint16_t CVECTOR_VERSION = 0x0101;

int clockId = -1;
uint8_t hour;
uint8_t min;
uint8_t sec;

void showClock()
{
    while (sec > 59)
    {
        sec = 0;
        min++;
    }
    while (min > 59)
    {
        min = 0;
        hour++;
    }
    while (hour > 12)
    {
        hour = 1;
    }
    printf("%02hd:%02hd:%02hd\r\n", hour, min, sec);
}

void clockCB(int timerId)
{
    timerId;
    sec++;
    showClock();
}

void main()
{
    char c;
    bool go = false;

    uartSupressRTS(false, false);

    if (clockId == -1)
    {
        clockId = timerGetAvailableTimerId();
    }
    if (clockId == -1)
    {
        return;
    }

    timerSet(clockId, 1000, true, false, clockCB);

    puts("'h' for hour, 'm' for minutes 'g' to go, 's' to stop");
    clockCB(clockId);
    while (!go)
    {
        c = getchar();
        switch (c)
        {
        case 'h':
            hour++;
            break;
        case 'm':
            min++;
            break;
        case 'M':
            min += 10;
            break;
        case 'g':
            timerRun(clockId);
            go = true;
            break;
        case 's':
            timerStop(clockId);
            go = true;
            break;
        }
        showClock();
    }

}


I'll need a menu function that switches a bank 1 and 2 in and then jumps to 0x4020. Also a menu item that lists all eligible banks with bank name and c vector version.

Sunday, September 20, 2015

Clock

I finally got around to starting to implement a clock feature in my menu program. I used the timer driver. It just waits for 1000ms and then outputs a time. To start, the menu goes into a loop that does getchar and then based on the letter pressed, does this:
'm': Increment minute
'M': Add 10 to minutes
'h': increment hour
'g': go--start the clock
's': stop--stops the clock

It works.

Thursday, September 17, 2015

Ticks

Last night, I decided to wire in the 1ms ticker. Of course, as soon as it wired in, the whole board stopped working. This morning I figured out that I had the INT and SCK lines swapped. So I unswapped them. I have already compiled in the tick driver so it should be grabbing the 3 bits of tick from the ticker. I verified that it is doing that when the UART interrupts occur.

I also verified that the INT pulls a pull-up low. But... I forgot that I had swapped out the 2MHz oscillator for a 4MHz oscillator. So my 1ms tick was actually 500μs. That was easy to fix. I ran home and grabbed my JTAG program and reburned the CPLD.

Now I had a 1ms tick. It wasn't counting correctly, but my code to handle the count wasn't right. Once that was corrected, it counted correctly. I can now make clocks.

However, there was an issue when I tried to program an intel hex file into flash--the tick makes the CPU too busy to keep up with communication. So, I rerouted the timer CPLD INT through a DIP switch. For now, I'll just turn off the timer when I want to program the flash. Until I can figure out a better way. I also have the 8MHz clock timer ready to burn. I may also toy with different tick amounts such as 2ms, or 5ms, or even 10ms.

Just for reference, here is the CPLD verilog:

// This CPLD is a timer circuit that generates a 500Hz tick. It takes a 2MHz
// clock in. Scales that down to 125MHz with a 4 bit counter.
// Then it counts 125 of those in a 7 bit counter that resets on 7c.
// When the reset occurs, the pin_nINT line goes from Hi-Z to low.
//
// The falling edge of pin_nCS pin brings pin_nINT back to Hi-Z.
//
// SCK and Dout are used to read the 3 bit tick counter. This is used
// to ensure that the count is kept synched. Dout is updated on the
// falling edge of SCK and the MSB is shifted first. 
//

`define COUNTER_TOP 11

module timer (
    input pin_CLK,
    input pin_nCS,
    input pin_SCK,
    output pin_Dout,
    output pin_nINT
    );

    reg [`COUNTER_TOP:0] counter;
    reg [2:0] ticks;
    reg [2:0] tickShift;
    wire nTick;
    reg nInt;
    reg nCS0;
    reg nCS1;
    reg SCK0;

    // Not synthesized. Just for testing
    initial begin
        counter = 0;
        ticks = 0;
    end

    // @ 2MHz 1ms, 124
    // @ 4MHz 1ms, 249
    // @ 8MHz 1ms, 499
    //assign nTick = ~(counter ==   11'b11111001111); // 124'15 causes 125'0->0'0
    assign nTick = ~(counter ==  12'b111110011111); // 124'31 causes 250'0->0'0
    //assign nTick = ~(counter == 13'b1111100111111); // 124'63 causes 500'0->0'0
    assign pin_nINT = (nInt == 1'b1) ? 1'bz : 1'b0;
    assign pin_Dout = pin_nCS ? 1'bz : tickShift[2];

    always @(posedge pin_CLK) begin
        if (nTick == 1'b0) begin
            counter <= 0;
            ticks <= ticks+1;
        end
        else begin
            counter <= counter+1;
        end

        nCS0 <= pin_nCS;
        nCS1 <= nCS0;

        // Set interrupt on the CLK. Reset it on the falling edge of
        // pin_nCS+2 clocks
        if (nTick == 1'b0) begin
            nInt <= 1'b0; 
        end
        else if ((nCS1 & (~nCS0)) == 1'b1) begin
            nInt <= 1'b1; 
        end

        // search for falling edge of SCK in CLK
        SCK0 <= pin_SCK;
        if (~pin_nCS) begin
            if (SCK0 & ~pin_SCK) begin
                tickShift[2:0] <= { tickShift[1:0], 1'b0 };
            end
        end
        else begin
            tickShift[2:0] <= ticks[2:0];
        end

    end

endmodule

Here is tick.c:

#include "tick.h"
#include "spi.h"
#include "gpio.h"
#include "idle.h"
#include "uart.h"
#include "timer.h"
#include "interrupt.h"

Z80_IO_PORT(GPIO_OUT, 0x80);
Z80_IO_PORT(SPI_REG, 0x83);
Z80_IO_PORT(SPI_CTRL, 0x84);

uint8_t lastTick = 0;
uint32_t totalTicks = 0;

void tickInit()
{
    // GPIO-0 is UART CS#
    gpioWriteLevel(0x02, 0x02);
    gpioWriteDirection(0x02, 0x02);
    lastTick = 0;
    totalTicks = 0;
}

uint32_t getTicks()
{
    uint32_t ticks;
    di();
    ticks = totalTicks;
    ei();
    return ticks;
}

// called from the actual ISR, so this one doesn't end in reti
void tickISR()
{
    uint8_t tick;
    uint8_t out;

    //tick = spiExchange(0, SPI_SHIFT_TO_MSB, 3);
    out = GPIO_OUT;
    GPIO_OUT = out & ~(1 << 1);
    SPI_REG = 0;
    SPI_CTRL = SPI_SHIFT_TO_MSB | 3;
    __asm__("nop");
    __asm__("nop");
    tick = SPI_REG;
    tick &= 0x07;
    GPIO_OUT = out;

    if (lastTick != tick)
    {
        totalTicks += (uint8_t)((tick - lastTick) & 0x07);
        lastTick = tick;
        timerTick(totalTicks);
        releaseIdle();
    }
}

So, next I have to start working with the timers and idle. One of my short term goals was to make a clock...now that's possible. Also, timeouts for communication.

I haven't used the timer code or idle yet. Here they are--probably buggy as hell since they are totally not tested.

timer.c:

#include "timer.h"
#include "tick.h"
#include <string.h>
#include <assert.h>

#define TIMER_COUNT 2

struct TimerInfo
{
    uint32_t interval;
    uint32_t trigger;
    bool claimed;
    bool repeat;
    bool running;
    void(*callback)(int timer);
};

struct TimerInfo timers[TIMER_COUNT];



void timerInit()
{
    memset(timers, sizeof(timers), 0);
}

// called from inside interrupt
void timerTick(uint32_t totalTicks)
{
    int timerId;
    for (timerId = 0; timerId < TIMER_COUNT; timerId++)
    {
        struct TimerInfo* timer = &timers[timerId];
        if (timer->claimed)
        {
            if (timer->running)
            {
                if ((int)(totalTicks - timer->trigger) >= 0)
                {
                    // tick has reached the trigger
                    if (timer->repeat)
                    {
                        timer->trigger += timer->interval;
                    }
                    if (timer->callback)
                    {
                        timer->callback(totalTicks);
                    }
                }
            }
        }
    }
}

int timerGetAvailableTimerId()
{
    int timerId = -1;
    int i;
    for (i = 0; i < TIMER_COUNT; i++)
    {
        struct TimerInfo* timer = &timers[timerId];
        if (!timer->claimed)
        {
            timer->claimed = 1;
            timerId = i;
            break;
        }
    }
    return timerId;
}

void timerFree(int timerId)
{
    assert(timerId > 0 && timerId < TIMER_COUNT);
    timers[timerId].claimed = 0;
}

void timerSet(int timerId, uint32_t interval, bool repeat, bool start, void(*cb)(int timer))
{
    struct TimerInfo* timer;
    assert(timerId > 0 && timerId < TIMER_COUNT);
    timer = &timers[timerId];
    assert(timer->claimed);
    timer->interval = interval;
    timer->repeat = repeat;
    timer->running = start;
    timer->callback = cb;
    timer->trigger = getTicks() + interval;
}

void timerGet(int timerId, uint32_t* pTrigger, uint32_t* pInterval, bool* pRepeat, bool* pRunning, void(**pCb)(int timer))
{
    struct TimerInfo* timer;
    assert(timerId > 0 && timerId < TIMER_COUNT);
    timer = &timers[timerId];
    if (pTrigger)
    {
        *pTrigger = timer->trigger;
    }
    if (pInterval)
    {
        *pInterval = timer->interval;
    }
    if (pRepeat)
    {
        *pRepeat = timer->repeat;
    }
    if (pRunning)
    {
        *pRunning = timer->running;
    }
    if (pCb)
    {
        *pCb = timer->callback;
    }
}

void timerStop(int timerId)
{
    struct TimerInfo* timer;
    assert(timerId > 0 && timerId < TIMER_COUNT);
    timer = &timers[timerId];
    timer->running = 0;
}

void timerRun(int timerId)
{
    struct TimerInfo* timer;
    assert(timerId > 0 && timerId < TIMER_COUNT);
    timer = &timers[timerId];
    timer->running = 1;
}

void timerResetInterval(int timerId)
{
    struct TimerInfo* timer;
    assert(timerId > 0 && timerId < TIMER_COUNT);
    timer = &timers[timerId];
    timer->trigger = getTicks() + timer->interval;
}


idle.c:

#include "idle.h"
#include "interrupt.h"

volatile bool idling;

// DO NOT CALL FROM CRITICAL SECTION
void idle(void(*pIdleFn)(void* p), void* p)
{
    idling = true;
    // assert(interrupts enabled);

    while (idling)
    {
        // spinwait for tick to set idling to false
        if (pIdleFn)
        {
            pIdleFn(p);
        }
    }
}

void releaseIdle()
{
    idling = false;
}



Wednesday, September 16, 2015

UART Driver Improved But Still Slow

I did a lot of scoping of the UART driver. And while I got some minor improvements by hand optimizing some slow parts, the driver is still not super swift. I still have a few questions, about some things, but I also added a tweak--I made it possible to suppress echo on gets. Now when the intel hex file goes across, instead of echoing it back, I just output a '.' to show that something was programmed. This makes the process fairly quick. I still need a to improve the speed of the flash activation.

Also, I have been using the native facilities to program the flash. I no longer use the Arduino. The wires between Arduino and the board are soldered into the board, but I have plans for that. But for now, the Arduino is docked to the side of my board. The computer runs with it not powered up.

Tuesday, September 15, 2015

Bank Switch Works But UART Driver Has An Issue

I got the bank switch stuff working. The interrupt.c wasn't hooking up to the membank.c ISR. So it was naturally doing the bankSwitch, but not the bankSwitchAndJump. Once it was properly hooked up, it started working. Now I can load an image into a flash bank and jump to it. That will be useful.

I also am puzzled that my communication seems so slow. Indeed, when the UART starts to send, it takes 3.2ms before it starts to send the next byte. The interrupt is asserted almost immediately after the send and it is quickly managed. So, somehow, I'm losing the interrupt on transmit buffer empty without filling it and then filling it at some later time...?

Anyway, I'm now bringing all my powers of deduction on that problem. I'm also thinking of not echoing gets for intel hex programming. I already have a 3 extension function for limiting the buffer size. Limiting echo is just one more such function. Combined with an assembly flash activation function call, this should make flash programming much quicker.....

Well, off to solve my problems...

More Menu Goodness

I'm working on making the menu more useable. It's starting to take a while to load the program on the Arduino. But it takes 11 seconds just to copy 16k into flash natively. I could turn the flash activate into assembly to speed it up quite a bit.

I have also added a menu item that just lets me run from RAM. It asks for a RAM bank, puts into bank 1, copies 0-3fff to 4000, then puts it into bank 0 restoring the original bank 1.

The separate copy RAM and copy flash are now one menu item that looks at the banks to decide how to copy.

I added GPIO access.

Also, I am in the process of adding a diagnostic system. This will let me set up diagnostic points that will either output data on the SPI bus or modify GPIO pins. I'll be able to activate and deactivate them. This should help me debug with the scope.

Finally, as can be seen below, I am adding build times for the libraries and application.


Mark Hamann's Z80 Computer

App Build: 21:04:14 Sep 15 2015
BSP Build: 21:04:13 Sep 15 2015
C Lib Build: 08:14:07 Sep 15 2015
Menu

1) run from RAM
2) dump mem
3) copy block
4) erase sectors
5) program byte
6) write byte
7) bank switch
8) program intel hex
9) set diagnostics
10) gpio
> 1

And now I have a menu item (not shown above) that lets me swap a flash bank into bank 0 and run from 0. When I do that, I need to hit enter to see the initial menu--so something is still a not quite right, But it's close....

Monday, September 14, 2015

Native Flash Programming Works!

I got native flash programming to work. It's not super fast,. but it's faster than the Arduino. And less complicated---errr it has the potential to be less complicated. Right now I have to manually copy the 0x0000-0x4000 to 0x8000 and then swap that bank into 0x0000. This means that the program is now running out of RAM instead of flash. And I need that if I want to be able to program or erase the flash.

It's also a manual input as to whether the intel hex module should program flash or write RAM. But really, I should be able to use the address to lookup the bank registers and use that.

So I have some tweaking to do.

I'm not sure I want to disconnect my Arduino quite yet. I do want the real estate to mount the SD card socket--but that's for future fun stuff.

I also tried to up the baud rate to 19200, but that didn't work. I'm a bit puzzled. 9600 baud is a character per ms. 19200 is 2 per ms. Can I really not get at the UART in time? Something is strange. If I can figure that out, maybe I can speed up communication.

Anyway, off to improve my menu system before I do too much more. Make it more useful and less cumbersome to use.

Then, I guess it's finally time to start getting the 1ms tick CPLD hooked up and the timers working. Then I just need a crt1 that I'll use for applications that use 4000-7fff in flash and 8000-bfff in RAM but that use C library functions in 0100-3fff.