| using Antmicro.Renode.Core; | |
| using Antmicro.Renode.Logging; | |
| using Antmicro.Renode.Peripherals.Bus; | |
| namespace Antmicro.Renode.Peripherals | |
| { | |
| public class Mailbox : IDoubleWordPeripheral, IKnownSize | |
| { | |
| //-------------------------------------------------------------------------- | |
| public Mailbox() | |
| { | |
| this.Log(LogLevel.Noisy, "Mailbox()"); | |
| mailbox_error = new GPIO(); | |
| mailbox_hiwater = new GPIO(); | |
| mailbox_lowater = new GPIO(); | |
| } | |
| public void Reset() // IPeripheral | |
| { | |
| for (int i = 0; i < FIFO_SIZE; i++) fifo[i] = 0; | |
| } | |
| //-------------------------------------------------------------------------- | |
| public uint ReadDoubleWord(long offset) // IDoubleWordPeripheral | |
| { | |
| uint result = 0; | |
| switch (offset) | |
| { | |
| case REG_ISTATE: { | |
| this.Log(LogLevel.Noisy, "read reg_istate = 0b{0}", System.Convert.ToString(reg_istate, 2)); | |
| result = reg_istate; | |
| break; | |
| } | |
| case REG_IENABLE: { | |
| this.Log(LogLevel.Noisy, "read reg_ienable = 0b{0}", System.Convert.ToString(reg_ienable, 2)); | |
| result = reg_ienable; | |
| break; | |
| } | |
| case REG_ITEST: { | |
| this.Log(LogLevel.Noisy, "read reg_itest = 0b{0}", System.Convert.ToString(reg_itest, 2)); | |
| result = reg_itest; | |
| break; | |
| } | |
| case REG_WRITE: { | |
| this.Log(LogLevel.Error, "Tried to read from the mailbox write register"); | |
| reg_istate |= IRQ_ERROR; break; | |
| } | |
| case REG_READ: | |
| if (fifo_count == 0) | |
| { | |
| this.Log(LogLevel.Error, "Tried to read from empty mailbox"); | |
| reg_istate |= IRQ_ERROR; | |
| reg_error |= ERR_READ; | |
| break; | |
| } | |
| else | |
| { | |
| this.Log(LogLevel.Noisy, "read fifo[{0}] = 0x{1:X8}, fifo_count {2}", cursor_r, fifo[cursor_r], fifo_count - 1); | |
| result = fifo[cursor_r]; | |
| cursor_r = (cursor_r + 1) & FIFO_MASK; | |
| fifo_count--; | |
| if (fifo_count <= reg_lowater) reg_istate |= IRQ_LOWATER; | |
| break; | |
| } | |
| case REG_STATUS: { | |
| this.Log(LogLevel.Noisy, "read reg_status = 0b{0}", System.Convert.ToString(reg_status, 2)); | |
| result = reg_status; | |
| break; | |
| } | |
| case REG_ERROR: { | |
| // Reading clears pending errors. | |
| this.Log(LogLevel.Noisy, "read reg_error = 0b{0}", System.Convert.ToString(reg_error, 2)); | |
| result = reg_error; | |
| reg_error = 0; | |
| break; | |
| } | |
| case REG_HIWATER: { | |
| this.Log(LogLevel.Noisy, "read reg_hiwater = {0}", reg_hiwater); | |
| result = reg_hiwater; | |
| break; | |
| } | |
| case REG_LOWATER: { | |
| this.Log(LogLevel.Noisy, "read reg_lowater = {0}", reg_lowater); | |
| result = reg_lowater; | |
| break; | |
| } | |
| case REG_FLUSH: { | |
| this.Log(LogLevel.Error, "Tried to read from the mailbox flush register"); | |
| reg_istate |= IRQ_ERROR; | |
| break; | |
| } | |
| } | |
| update_regs(); | |
| return result; | |
| } | |
| //-------------------------------------------------------------------------- | |
| public void WriteDoubleWord(long offset, uint value) // IDoubleWordPeripheral | |
| { | |
| switch (offset) | |
| { | |
| case REG_ISTATE: { | |
| // Interrupt state is write-to-clear. | |
| uint new_istate = (reg_istate & ~value) & IRQ_MASK; | |
| this.Log(LogLevel.Noisy, "write reg_istate 0b{0} -> 0b{1}", System.Convert.ToString(reg_istate, 2), System.Convert.ToString(new_istate, 2)); | |
| reg_istate = new_istate; | |
| break; | |
| } | |
| case REG_IENABLE: { | |
| this.Log(LogLevel.Noisy, "write reg_ienable 0b{0} -> 0b{1}", System.Convert.ToString(reg_ienable, 2), System.Convert.ToString(value & IRQ_MASK, 2)); | |
| reg_ienable = value & IRQ_MASK; | |
| break; | |
| } | |
| case REG_ITEST: { | |
| this.Log(LogLevel.Noisy, "write reg_itest 0b{0} -> 0b{1}", System.Convert.ToString(reg_itest, 2), System.Convert.ToString(value & IRQ_MASK, 2)); | |
| reg_itest = value & IRQ_MASK; | |
| break; | |
| } | |
| case REG_WRITE: | |
| if (fifo_count == FIFO_SIZE) | |
| { | |
| this.Log(LogLevel.Error, "Tried to write to full mailbox"); | |
| reg_istate |= IRQ_ERROR; | |
| reg_error |= ERR_WRITE; | |
| break; | |
| } | |
| else | |
| { | |
| this.Log(LogLevel.Noisy, "write fifo[{0}] = 0x{1:X8}, fifo_count {2}", cursor_w, value, fifo_count + 1); | |
| fifo[cursor_w] = value; | |
| cursor_w = (cursor_w + 1) & FIFO_MASK; | |
| fifo_count++; | |
| if (fifo_count >= reg_hiwater) reg_istate |= IRQ_HIWATER; | |
| break; | |
| } | |
| case REG_READ: { | |
| this.Log(LogLevel.Error, "Tried to write 0x{0:X8} to the mailbox read register", value); | |
| reg_istate |= IRQ_ERROR; | |
| break; | |
| } | |
| case REG_STATUS: { | |
| this.Log(LogLevel.Noisy, "write 0x{0:X8} to reg_status", value); | |
| reg_istate |= IRQ_ERROR; | |
| break; | |
| } | |
| case REG_ERROR: { | |
| this.Log(LogLevel.Noisy, "write 0x{0:X8} to reg_status", value); | |
| reg_istate |= IRQ_ERROR; | |
| break; | |
| } | |
| case REG_HIWATER: { | |
| this.Log(LogLevel.Noisy, "write reg_hiwater {0} -> {1}", reg_hiwater, value); | |
| reg_hiwater = System.Math.Min(value, FIFO_SIZE - 1); | |
| break; | |
| } | |
| case REG_LOWATER: { | |
| this.Log(LogLevel.Noisy, "write reg_lowater {0} -> {1}", reg_lowater, value); | |
| reg_lowater = System.Math.Min(value, FIFO_SIZE - 1); | |
| break; | |
| } | |
| case REG_FLUSH: | |
| this.Log(LogLevel.Noisy, "write reg_flush = 0b{0}", System.Convert.ToString(value, 2)); | |
| // FIXME - not 100% sure that flushing makes sense w/ this setup... | |
| if ((value & FLUSH_READ) != 0) fifo_count = 0; | |
| if ((value & FLUSH_WRITE) != 0) fifo_count = 0; | |
| break; | |
| } | |
| update_regs(); | |
| } | |
| //-------------------------------------------------------------------------- | |
| public void update_regs() | |
| { | |
| reg_status = 0; | |
| if (fifo_count == 0) reg_status |= STATUS_EMPTY; | |
| if (fifo_count == FIFO_SIZE) reg_status |= STATUS_FULL; | |
| if (fifo_count <= reg_lowater) reg_status |= STATUS_LOWATER; | |
| if (fifo_count >= reg_hiwater) reg_status |= STATUS_HIWATER; | |
| bool new_irq_error = ((reg_ienable & IRQ_ERROR) != 0) && ((reg_istate & IRQ_ERROR) != 0); | |
| bool new_irq_hiwater = ((reg_ienable & IRQ_HIWATER) != 0) && ((reg_istate & IRQ_HIWATER) != 0); | |
| bool new_irq_lowater = ((reg_ienable & IRQ_LOWATER) != 0) && ((reg_istate & IRQ_LOWATER) != 0); | |
| if (!mailbox_error.IsSet && new_irq_error) this.Log(LogLevel.Noisy, "Triggering error IRQ"); | |
| if (!mailbox_hiwater.IsSet && new_irq_hiwater) this.Log(LogLevel.Noisy, "Triggering hiwater IRQ"); | |
| if (!mailbox_lowater.IsSet && new_irq_lowater) this.Log(LogLevel.Noisy, "Triggering lowater IRQ"); | |
| if (mailbox_error.IsSet && !new_irq_error) this.Log(LogLevel.Noisy, "Clearing error IRQ"); | |
| if (mailbox_hiwater.IsSet && !new_irq_hiwater) this.Log(LogLevel.Noisy, "Clearing hiwater IRQ"); | |
| if (mailbox_lowater.IsSet && !new_irq_lowater) this.Log(LogLevel.Noisy, "Clearing lowater IRQ"); | |
| mailbox_error.Set(new_irq_error); | |
| mailbox_hiwater.Set(new_irq_hiwater); | |
| mailbox_lowater.Set(new_irq_lowater); | |
| } | |
| //-------------------------------------------------------------------------- | |
| const uint FIFO_SIZE = 8; | |
| const uint FIFO_MASK = FIFO_SIZE - 1; | |
| const uint REG_ISTATE = 0x000; | |
| const uint REG_IENABLE = 0x004; | |
| const uint REG_ITEST = 0x008; | |
| const uint REG_WRITE = 0x00C; | |
| const uint REG_READ = 0x010; | |
| const uint REG_STATUS = 0x014; | |
| const uint REG_ERROR = 0x018; | |
| const uint REG_HIWATER = 0x01C; | |
| const uint REG_LOWATER = 0x020; | |
| const uint REG_FLUSH = 0x024; | |
| const uint STATUS_EMPTY = 0b0001; | |
| const uint STATUS_FULL = 0b0010; | |
| const uint STATUS_HIWATER = 0b0100; | |
| const uint STATUS_LOWATER = 0b1000; | |
| const uint STATUS_MASK = 0b1111; | |
| const uint ERR_READ = 0b01; | |
| const uint ERR_WRITE = 0b10; | |
| const uint IRQ_ERROR = 0b001; | |
| const uint IRQ_HIWATER = 0b010; | |
| const uint IRQ_LOWATER = 0b100; | |
| const uint IRQ_MASK = 0b111; | |
| const uint FLUSH_READ = 0b01; | |
| const uint FLUSH_WRITE = 0b10; | |
| //-------------------------------------------------------------------------- | |
| private uint reg_istate; | |
| private uint reg_ienable; | |
| private uint reg_itest; | |
| private uint reg_mboxw; | |
| private uint reg_mboxr; | |
| private uint reg_status; | |
| private uint reg_error; | |
| private uint reg_lowater; | |
| private uint reg_hiwater; | |
| private uint cursor_r = 0; | |
| private uint cursor_w = 0; | |
| private uint fifo_count = 0; | |
| private uint[] fifo = new uint[FIFO_SIZE]; | |
| public GPIO mailbox_error { get; } | |
| public GPIO mailbox_hiwater { get; } | |
| public GPIO mailbox_lowater { get; } | |
| public long Size => 40; // IKnownSize | |
| //-------------------------------------------------------------------------- | |
| } | |
| } |