-
C cross-assembly experiments
06/06/2017 at 14:41 • 0 commentsI started writing some C code to see if I can do what I want it to. It didn't take long to realize that I was really just writing an assembler. However, it's an assembler where I can write my own macros, so it's pretty handy. The macros really end up looking like instructions. I use a giant array to hold the code. This is easier than file I/O and string processing. And each memory location gets an optional label in case I want to jump there. My one instruction routine is just:
void move(uint16_t src, uint16_t dst) { code[codeIndex].byte = src; codeIndex++; code[codeIndex].byte = dst; codeIndex++; }
And I can make macros like this:/* load: moves a constant into a destination */ void load(uint16_t constant, uint16_t dst) { move(constant,LOAD); move(LOAD,dst); }
I can add labels and jump to them:void label(char labelstring[20]) { strcpy(code[codeIndex].label,labelstring); } void jump(char labelstring[20]) { uint16_t i; for (i=codeStart;i<codeIndex;i+=2) { if (strcmp(labelstring,code[i].label)==0) { load((i/256),PCTEMP); load((i%256),PCLO); return; } } strcpy(code[codeIndex].missingUpper,labelstring); load(0x00,PCTEMP); strcpy(code[codeIndex].missingLower,labelstring); load(0x00,PCLO); }
As you can see I need to handle missing labels. So I do a second pass (just like a real assembler) afterwards to find the missing labels and insert them into the code.
So my programs end up looking like this:void aluTester(uint16_t start) { if (0!=start) { codeStart = start; codeIndex = codeStart; } label("ALU_TEST"); load(0x35, ALUA); load(0x03, SUB); move(SUB, UDAT); jump("ALU_TEST"); }
This is much more readable and usable than writing in just assembly. The start variable is the address to put the code in memory. All of this allows me to write macros the "right way". I was hacking in the code to make it small, but not as modular. For example, if I knew I was jumping within the same page, I wouldn't update the page register (PCTEMP). With this code, it always writes the page register before a jump. So the programs end up being longer. However, I can always go back and optimize the jump routine to keep track of what page it is on and optionally write there more intelligently.This is already working so well, I believe it's my path forward. I have one simple function which outputs an easy-to-read display of instructions, and one which will output in a format that I can copy/paste to the bootloader running on the FPGA dev board. We are really rolling now!
-
Straight to C?
06/04/2017 at 17:11 • 2 commentsSince I've been writing in "assembly", I remember how much I don't like writing in assembly. It's just so limiting. This processor doesn't even have microcode, so it takes a lot of instructions to do just about anything. It would be very handy to implement macros. I'm thinking I may skip the assembler and go straight to C functions. I could write a simple function which takes two bytes, and outputs the machine code to a file. And then use that function in larger functions. So if I want to move a constant to ALUA which is normally two instructions, I could write a macro to do it in one. That's sounding a lot more appealing. There's a lot to think about for where to put the instructions in memory and doing jumps to labels. I guess that's about the same as writing an assembler, but I get to stay in C. Is there a term for this type of assembler? not quite assembly language and not quite C.
-
Bootloader working!
06/02/2017 at 18:03 • 0 commentsAfter a good bit of work, I finally have the simpler bootloader working. It's a total of 370 bytes - a far cry from some of the very simple bootloaders for like the Altair 8800. I decided to write the first program I got working to RAM since it was small and simple. It does a quick 35-3 and writes the result to the UART, so repeats the character "2" over and over. The program is 12 bytes, so requires 12 write commands, and the the jump command. I put the program at 0x9000 since nothing else is there.
I also continue to uncover bugs in the VHDL, so it's not just a software effort. I've also started ordering some parts for the solderless breadboard version to start building. At the same time I've been looking into assemblers from previous suggestions and even looking into doing one from scratch. It will be super slow trying to convert assembler to machine code by hand to enter into the bootloader, so I'll need something for sure.
-
Smaller bootloader
05/30/2017 at 16:59 • 0 commentsOk, writing code like this is a real pain. I think I need to shorten my bootloader to get it up and running faster. Maybe after I get it going I will expand it. I know with old computers where you had to input the bootloader via switches on the front panel, it was highly desirable to write the shortest possible bootloader. So now my plan is:
- Echo characters back to uart (DONE!)
- When a key is pressed, put it at string index, and increase string index
- Backspace key will decrease string index
- Jump to string processing after carriage return entered
- 1st character is command (W - write, J - jump) (anything else jumps back to top)
- if write then
- Next four hexnumbers are the memory address (convert from ASCII)
- Last 2 hexnumbers are data to write (convert from ASCII)
- if a jump,
- Next four hexnumbers are the memory address (convert from ASCII)
- set PC to entered memory address (begin executing!)
So this code must be entered with the exact number of characters (5 or 7). Backspace will still work. It will not echo back the string afterward. Once I get this going, then I can enhance it later. Even this is somewhat complicated. But I could write a better bootloader later. That's how it used to be - very simple bootloader to better bootloader to better bootloader.
If I automated sending code, then I could get rid of the ASCII characters, enter it in binary, get rid of the backspace and echoing of characters. So I could streamline this even more, but I think it's ok as is.
-
Stack working!
05/25/2017 at 14:48 • 0 commentsI have my stack working. It takes a lot of lines to do a pop/push, but that's the nature of a RISC or OISC. I thought about having two dedicated registers for the stack pointer, but I really don't want to increase the hardware complexity when I can do it in software. I assigned the stack pointer to memory locations 0xFFFE and 0xFFFF. Since it contains a memory location, it needs two bytes.
It initially points to 0xFFFD. So it points to the next available memory location (empty stack style). Then the stack grows down. Pushing to the stack will write the byte first, then increase the stack pointer. Popping from the stack will decrease the stack pointer first, then read the byte there.
Ok, I have the first test of the routine hardwired to return to a specific address, so I need to update it to grab the Program Counter and then add an offset. The offset changes as I refine the push function, but I can handle that.
I found that my string output function would be really handy if I could push a memory address onto the stack which contains the pointer to the string, and then call the function (pushing the return address on the stack). So my string function would grab the string pointer off the stack, and at the end pop the return address off the stack.
-
Bootloader
05/23/2017 at 15:58 • 0 commentsI think before I get into writing other software, I should focus on a bootloader. I don't like to keep synthesizing my VHDL every time I want to change my program. I need to poke the RAM and execute the program, so I have to give it a memory location and a byte. And it would be VERY useful to have a readback function, but I could do this afterward. So I put together a list of things my bootloader needs to do:
- Echo characters back to uart (DONE!)
- Keep track of string entered
- Process backspace key (subtract one from string pointer if not zero)
- Process enter key (don't store in string)
- Echo command on a new line (DONE!)
- Jump to string processing
- Split string into parts: 1st character is command (W - write, R - read, J - jump) (anything else jumps back to top)
- Skip anything not a hexnumber (so I can have whitespaces and extra stuff for readability)
- Next four hexnumbers are the memory address
- If a write, next two hexnumbers are data to write to the above address
- if a jump command, set PC to entered memory address (begin executing!)
- If a read, grab the memory contents and output them to the uart (converting to ASCII)
So I have my task list here. It should be easy to test as I go. And I can use everything I've written so far.
-
return of the string (output)
05/23/2017 at 01:32 • 0 commentsFinally got my string output working! I found a bug with my compare bit. Now I really need to work on how to call functions and return from them. Then I use this string output as a function to send information over the serial port. It significantly helps my debug capability. To return from function calls, I need to have a simple stack. I'll need to pull the program counter and push it on the stack. I can write a stack in software. It's not necessarily a hardware-only thing even though many processors do it in hardware. The 6502 has a dedicated instruction for it. This means I need to start structuring my RAM. I've never written a software stack, but I know you need a pointer to the top of the stack. And then popping and pushing will need to decrease and increase this pointer. And I'll have to do it a lot, so it'll need to be simple.
-
Pointer oversight
05/21/2017 at 03:14 • 0 commentsI realized I need to use the pointer register to override both the destination and source register. I've finished writing a piece of code which grabs a string from ROM and puts it out to the UART. I use an index variable to the string. So I want to copy that to the pointer register, and then copy the pointer data (the ROM character) to the UART. I can't do it easily in the TTL version because that would mean putting the source and destination and the pointer address to the same bus. So the source and destination would drive onto the same bus at the same time. But I think I will need it. This is why I wanted to start writing some software. You find out very quickly what you need in the hardware when you start writing.
-
Build plan
05/19/2017 at 21:49 • 0 commentsSo after being on a pesky vacation for a week, I put together a plan for construction of the TTL version. I wanted to be able to test as I go, so I figured I'd order the module construction sequence so I could check the system functionality before moving to the next part. Now I just have to acquire the parts...
- Clock circuit with single-step mode
- Program counter (low byte only)
- Instruction ROM
- Src and Dst registers
- Control state machine - 3-bit shift reg
- Decoding ROMs
- Load reg
- ALU A
- ALU chips
- ALU result reg
- AEB register
- Pointer address register
- ROM data tristate buffer (read byte at PC)
- PC buffer
- Data Ram register (may need to modify to store lower 7 bits of either dst or src so they can be buffered to the ROM)
- RAM
- UART
- Expand PC and data ram high byte
-
Chip availability (oh TTL...)
05/12/2017 at 18:56 • 0 commentsI started thinking about how I'm going to implement the chip version of the project. I'm leaning toward solderless breadboard at first. And then maybe a board layout. So I started looking for chips to order online and ran into problems.
I've been able to find the chips I expected to use, but they are upwards of $60+ per chip. I guess I didn't check the price - only if they were available. And I wasn't expecting it. SOOO I may have to bend my rules a little. I've looked for parts which are drop in replacements even if they aren't active chips. So the spirit of the goal is intact if not the letter.
However, even those chips are little pricey. The register chips are $6 each, so I may replace them with a cheaper version where I don't need all the functionality. The 74LS374 Octal FF can work in a lot of places just fine and is only 79 cents. I believe I need 8 of these, so it's probably worth it to drop down when I don't need the extra functionality.
The 8-bit counters are $8.25. So I may drop down to the 74LS161 4-bit counters at 79 cents. I'll just need two for each 8-bit version which ups my chip count. So $16.50 for two or $3.16 for four. But there's only two of these, so it might be worthwhile to go with the more expensive version.
But at least I can find all the parts I need in some form or another. I'll have to make a mini-roadmap to build this up in small sections so I can debug and verify as I go.