Pano Logic G1 (3) - UART & Hard fault

2019-03-26

/uploads/blog/2019/1553805842911-1708821553560816238.jpg It has been a while since my last update. I have been working on the USB stuff for Pano Logic G1, mainly for connecting to joysticks and flash drives. I was concerned that my LPDDR and cache would cause me some trouble when the code is being executed in RAM, but so far they are holding up well. I will talk more about them in the next log. In this log I would like to talk a little about some debugging utilites, namely, the UART and the hard fault.

UART

UART is very handy when you want to see logs from the device. At first I thought I can get away just by using VGA text terminal, but it soon turns out that 80x30 text is simply not enough. Unfortunately the Pano Logic doesn't have any serial ports. From the schematics, it seems that they originally have one, but was removed after some revisions. But anyway, I have to repurpose the IOs to create myself a serial port to use.

This is not new to Pano Logic, Skip Hansen from PanoMan project has already done this: he soldered a wire to the LED pin and get the serial output from there. For me, as mentioned in one previous log, I do not have soldering iron with me currently, so I need to find some other way.

As an alternate, I used the wire clip come with my logic analyzer. They can be attached to through-hole components easily, such as this VGA connector.

I am using VGA SCL pin for the serial port. I wrote an extremely simple UART transmitter to transfer the data: https://github.com/zephray/VerilogBoy/blob/refactor/target/panog1/fpga/simple_uart.v. Why I don't just use Skip's UART transmitter core for Pano Logic G1? Or why I don't just work on top of Tom and Skip's project? Well, the (stupid) answer is the same regarding why I picked PicoRV32 rather than VexRiscv: I have decided (long ago) to call this project VerilogBoy, so all the source code should be written in Verilog, not SpinalHDL. Yes I am also aware there are tons of better open-source UART controller written in Verilog available online. I am probably just too lazy to find one considering I don't need other fancy functions anyway.

Using this UART transmitter also very easy, hook it up to the bus and write to the only register available: data register. It has too possible operation modes depending on the way it is connected. If the ready signal is connected to the ready signal of the bus, the UART transmitter would block the code execution until it finishes the transmission. Means the UART print function can be as easy as:

#define UART_TXDR *(volatile uint32_t *)0x03000100
void uart_print(char *str) {
    while (*str) UART_TXDR = *str++;
}

There is no need to worry about if the transmission has finished or FIFO overrun whatever, since it simply throttle the write speed to transmission speed.

Alternatively, if the ready signal is connected to the external interrupt input of the CPU, it can generate a end of transfer interrupt, then a software FIFO can be implemented. Hard fault

Are you familiar with the Hard Fault in microcontrollers, or Segmentation Fault in Linux, or "The program has stopped working" in Windows? Usually they are referring to the same thing: the code is accessing some memory they shouldn't touch. Most commonly, dereferencing a null pointer (which is nothing more than a pointer points to address 0). I hate this error: they are so common, but not easy to debug. However, I have to admit, it is the one who points out the bug in my code, debugging would be even harder without anyone telling me where goes wrong.

1553805847182-6449801553560402125.jpg

(It simply hangs when such things happens. It can be hard to determine who caused the issue on real hardware.)

Unfortunately, the processor core itself cannot detect such errors, as it doesn't really know what is right or wrong. Generally, MMU or MPU is in charge of this: the program defines the valid address range (physical address or virtual address, depending on the specific environment.). The MMU or MPU would then generate an exception when an illegal memory access happens. On systems without MMU or MPU, this is generally implemented by the bus controller, accessing to unmapped address spaces would generate an exception. The PicoRV32 doesn't have a MMU or MMU, so I will implement it in the bus.

Sidenote 1: Hard fault on ARM microcontrollers doesn't necessarily mean an illegal memory access. Unaligned memory access, invalid instruction and few other exception will also generate a hard fault. Illegal memory access just tends to be the most common case.

Sidenote 2: On ARM microcontrollers, even if the MPU is not enabled, acessing unmapped memory space would still generate a hard fault, which is nice.

Sidenote 3: I used to implement such a bus controller for 80386, but it didn't work. The 80386 has a prefetch queue, which would blindly fetch instructions after the current EIP. It is very easy for it to fetch things outside of valid memory space. I would assume the same for most modern processors: you probably cannot implement such protections outside of a processor. The PicoRV32 is just simple enough so I am able to implement this.

Codewise, it is trivial: catch all access to unmmapped space, and generate an interrupt.

    reg mem_valid_last;
    always @(posedge clk_rv) begin
        if (!rst_rv)
            cpu_irq <= 1'b0;
        else begin
            mem_valid_last <= mem_valid;
            if (mem_valid && !mem_valid_last && !(ram_valid || vram_valid || gpio_valid || usb_valid || uart_valid || ddr_valid))
                cpu_irq <= 1'b1;
            else
                cpu_irq <= 1'b0;
        end
    end

Inside the firmware, create a hard fault handler,

irq:
# copy the source PC (inside gp register) to argument 0 (a0 register)
addi a0, gp, 0
# call handler
call hard_fault_handler
void hard_fault_handler(uint32_t pc) {
    // External logic is required to connect fault signal to IRQ line,
    // and ENABLE_IRQ_QREGS should be turned off.
    term_print("HARD FAULT PC = ");
    term_print_hex(pc, 8);
    while (1);
}

Don't forget to enable the interrupt at startup:

    // Set interrupt mask to zero (enable all interrupts)
    // This is a PicoRV32 custom instruction
     asm(".word 0x0600000b");

Now try some program that will cause a hard fault, it should fail as expected:

1553806036814-6631831553560531841.png

Sidenote 4: Why I decided to call it Hard Fault rather than Segmentation Fault or Page Fault? Because my system does not have an MMU, so there are no memory pages, it also does not have memory segments as X86 processors do, so it is technically not a Seg Fault or Page Fault. Hard fault is just slightly more accurate. I know I should probably call it bus fault, but that's more like an after thought anyway. It works now so I am just going to leave it as is.

Sidenote 5: Don't put the Boot ROM or anything at address 0. This will make null pointer dereferencing legal and impossible to catch in our case. Guess who is doing that and spent a whole day chasing down some random crash...

Conclusion

Nothing much, really. I implemented both in half a day, just turns out to be helpful for debugging my stupid firmware code. Yes a full JTAG debugger or tracer would be much nicer, but I am running out of GPIOs for me to implement that. I am writting this just to give an update for the project, also sharing some of my ideas. Hope to see you soon in the next update. For the time being, thanks for reading.