Skip to content

Commit f4c1f6f

Browse files
committed
Use modulo (capacity + 1) instead of (capacity * 2) for UART buffers
1 parent c13e570 commit f4c1f6f

File tree

9 files changed

+152
-103
lines changed

9 files changed

+152
-103
lines changed

README.md

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ Addresses `0xf0000000` - `0xffffffff` are reserved for system purposes such as M
3636

3737
Addresses `0xf0000010` and `0xf0000030` contain emulated UART 16550 peripherals based on [this datasheet](https://caro.su/msx/ocm_de1/16550.pdf). The UARTs support the following features:
3838

39-
- Configurable FIFO capacity (up to 254 bytes) for TX and RX, stored as a variable in the CONFIG processor.
40-
- Theoretical maximum transfer rate of 121920 bits/sec (254 bytes/tick).
39+
- Configurable FIFO capacity (up to 253 bytes) for TX and RX, stored as a variable in the CONFIG processor.
40+
- Theoretical maximum transfer rate of 121440 bits/sec (253 bytes/tick).
4141
- Line Status Register flags: Transmitter Empty, THR Empty, Overrun Error, Data Ready.
4242
- FIFO Control Register flags: Enable FIFOs (0 is ignored), Reset RX/TX FIFO
4343

@@ -58,20 +58,18 @@ The UART registers have a stride of 4 bytes to simplify some internal logic.
5858

5959
Each UART is implemented as two circular buffers in a memory bank with the following layout. Note that RX refers to data sent to / read by the processor, and TX refers to data sent from / written by the processor.
6060

61-
| Index | Name |
62-
| ----- | ----------------------- |
63-
| 0 | RX buffer start |
64-
| 254 | RX buffer read pointer |
65-
| 255 | RX buffer write pointer |
66-
| 256 | TX buffer start |
67-
| 510 | TX buffer read pointer |
68-
| 511 | TX buffer write pointer |
61+
| Index | Name |
62+
| ----- | --------------------------------------- |
63+
| 0 | RX buffer start |
64+
| 254 | RX buffer read pointer |
65+
| 255 | RX buffer write pointer / overflow flag |
66+
| 256 | TX buffer start |
67+
| 510 | TX buffer read pointer |
68+
| 511 | TX buffer write pointer |
6969

70-
Read/write pointers are stored modulo `2 * capacity` ([ref 1](https://github.com/hathach/tinyusb/blob/b203d9eaf7d76fd9fec71b4ee327805a80594574/src/common/tusb_fifo.h), [ref 2](https://gist.github.com/mcejp/719d3485b04cfcf82e8a8734957da06a)) to allow using the entire buffer capacity without introducing race conditions.
70+
Read/write pointers are stored modulo `capacity + 1`. A buffer is empty when `rptr == wptr` and full when `rptr == (wptr + 1) % (capacity + 1)`. If the RX buffer is full and more data arrives, producers should discard the new data rather than overwriting old data in the buffer. An overflow may optionally be indicated to the processor by setting bit 8 of `rx_wptr` (ie. `rx_wptr | 0x100`).
7171

72-
If the RX buffer is full and more data arrives, producers should discard the new data rather than overwriting old data in the buffer. An overflow may optionally be indicated by advancing the RX write pointer (without writing a value to the buffer) such that the size of the buffer is `capacity + 1`; this must be done atomically (ie. `wait` first) to avoid race conditions.
73-
74-
Note that the processor itself does not prevent code from overflowing the TX buffer. Users are expected to check the Line Status Register and avoid writing too much data at once.
72+
Note that the processor itself does not set the TX overflow flag or prevent code from overflowing the TX buffer. Users are expected to check the Line Status Register and avoid writing too much data at once.
7573

7674
### Syscon
7775

mod/src/main/kotlin/gay/object/mlogv32/ProcessorAccess.kt

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,12 @@ class ProcessorAccess(
3535
val resetSwitch: SwitchBuild,
3636
val pauseSwitch: SwitchBuild,
3737
val singleStepSwitch: SwitchBuild,
38-
val uartFifoCapacity: Int,
38+
uartFifoModulo: Int,
3939
uart0: MemoryBuild,
4040
uart1: MemoryBuild,
4141
) {
42-
val uart0 = UartAccess(uart0, uartFifoCapacity)
43-
val uart1 = UartAccess(uart1, uartFifoCapacity)
42+
val uart0 = UartAccess(uart0, uartFifoModulo - 1)
43+
val uart1 = UartAccess(uart1, uartFifoModulo - 1)
4444

4545
val romEnd = ROM_START + romSize.toUInt()
4646
val ramEnd = RAM_START + ramSize.toUInt()
@@ -253,7 +253,7 @@ class ProcessorAccess(
253253
resetSwitch = buildVar<SwitchBuild>(build, "switch1") ?: return null,
254254
pauseSwitch = buildVar<SwitchBuild>(build, "switch2") ?: return null,
255255
singleStepSwitch = buildVar<SwitchBuild>(build, "switch3") ?: return null,
256-
uartFifoCapacity = positiveIntVar(build, "UART_FIFO_CAPACITY") ?: return null,
256+
uartFifoModulo = positiveIntVar(build, "UART_FIFO_MODULO") ?: return null,
257257
uart0 = buildVar<MemoryBuild>(build, "bank1") ?: return null,
258258
uart1 = buildVar<MemoryBuild>(build, "bank2") ?: return null,
259259
)
@@ -399,16 +399,20 @@ data class UartRequest(
399399
val toUart = 0.until(rx.availableForRead).map { rx.readByte().toUByte() }
400400
if (toUart.isNotEmpty()) Log.info("Sending to $device: $toUart")
401401

402+
var overflowCount = 0
403+
402404
val fromUart = runOnMainThread {
403405
if (stopOnHalt && processor.resetSwitch.enabled) {
404406
throw RuntimeException("Processor stopped!")
405407
}
406408
for (byte in toUart) {
407-
uart.write(byte)
409+
if (!uart.write(byte)) overflowCount++
408410
}
409411
uart.readAll()
410412
}
411413

414+
if (overflowCount > 0) Log.warn("$device RX buffer is full, $overflowCount bytes dropped!")
415+
412416
if (fromUart.isNotEmpty()) Log.info("Received from $device: $fromUart")
413417
for (byte in fromUart) {
414418
tx.writeByte(byte.toByte())

mod/src/main/kotlin/gay/object/mlogv32/UartAccess.kt

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import kotlin.reflect.KProperty
66
class UartAccess(private val build: MemoryBuild, private val capacity: Int) {
77
// NOTE: rx and tx refer to our receiver and transmitter, not the processor's
88
private val txRptr by MemoryIntDelegate(build, 254)
9-
private var txWptr by MemoryIntDelegate(build, 255)
10-
private val txSize get() = (txWptr - txRptr).mod(capacity * 2)
9+
private var txWptrRaw by MemoryIntDelegate(build, 255)
10+
private val txWptr get() = txWptrRaw and 0xff
1111

1212
private var rxRptr by MemoryIntDelegate(build, 510)
1313
private val rxWptr by MemoryIntDelegate(build, 511)
@@ -25,19 +25,29 @@ class UartAccess(private val build: MemoryBuild, private val capacity: Int) {
2525
return null
2626
}
2727

28-
val byte = build.memory[256 + rxRptr.mod(capacity)].toInt().toUByte()
29-
rxRptr = (rxRptr + 1).mod(capacity * 2)
28+
val byte = build.memory[256 + rxRptr].toInt().toUByte()
29+
rxRptr = wrap(rxRptr + 1)
3030
return byte
3131
}
3232

33-
fun write(byte: UByte) {
34-
if (txSize < capacity) {
35-
build.memory[txWptr.mod(capacity)] = byte.toDouble()
36-
}
37-
if (txSize <= capacity) {
38-
txWptr = (txWptr + 1).mod(capacity * 2)
33+
fun write(byte: UByte, signalOverflow: Boolean = true): Boolean {
34+
val nextWptr = wrap(txWptr + 1)
35+
36+
// full, maybe signal overflow
37+
if (nextWptr == txRptr) {
38+
if (signalOverflow) {
39+
txWptrRaw = txWptr or 0x100
40+
}
41+
return false
3942
}
43+
44+
// not full, write a byte
45+
build.memory[txWptr] = byte.toDouble()
46+
txWptrRaw = nextWptr
47+
return true
4048
}
49+
50+
private fun wrap(index: Int) = index.mod(capacity + 1)
4151
}
4252

4353
class MemoryIntDelegate(

src/config/base.mlog.jinja

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@
1313
set RAM_SIZE {{#RAM_SIZE}} # RAM size in bytes (rwx)
1414
set ICACHE_SIZE {{#ICACHE_SIZE}} # icache size in variables, or bytes of memory it can represent; 4x less dense than ROM/RAM
1515

16-
set UART_FIFO_CAPACITY {{#UART_FIFO_CAPACITY}} # UART TX/RX FIFO capacity in bytes (max 254)
16+
set UART_FIFO_CAPACITY {{#UART_FIFO_CAPACITY}} # UART TX/RX FIFO capacity in bytes (max 253)
1717

1818
# computed values
19-
op add MEMORY_X @thisx MEMORY_X_OFFSET
20-
op add MEMORY_Y @thisy MEMORY_Y_OFFSET
19+
op add MEMORY_X @thisx MEMORY_X_OFFSET # absolute x position of bottom left memory proc
20+
op add MEMORY_Y @thisy MEMORY_Y_OFFSET # absolute y position of bottom left memory proc
21+
op add UART_FIFO_MODULO UART_FIFO_CAPACITY 1 # actual number of indices used for each FIFO, keeping one empty to check if the buffer is full (max 254)
2122

2223
stop
2324

@@ -30,7 +31,7 @@ set MEMORY_WIDTH MEMORY_WIDTH
3031
set ROM_SIZE ROM_SIZE
3132
set RAM_SIZE RAM_SIZE
3233
set ICACHE_SIZE ICACHE_SIZE
33-
set UART_FIFO_CAPACITY UART_FIFO_CAPACITY
34+
set UART_FIFO_MODULO UART_FIFO_MODULO
3435
set MEMORY_X MEMORY_X
3536
set MEMORY_Y MEMORY_Y
3637
# {% endraw %}

src/config/configs.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ configs:
1212
ROM_SIZE: '0x480000'
1313
RAM_SIZE: '0x480000'
1414
ICACHE_SIZE: '0xc0000'
15-
UART_FIFO_CAPACITY: 254
15+
UART_FIFO_CAPACITY: 253

src/debugger.mlog.jinja

Lines changed: 36 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -197,14 +197,16 @@ loop:
197197
read reservation_set CPU "reservation_set"
198198
read privilege_mode CPU "privilege_mode"
199199

200-
read UART_FIFO_CAPACITY CPU "UART_FIFO_CAPACITY"
201-
op mul UART_FIFO_MODULO UART_FIFO_CAPACITY 2
200+
read UART_FIFO_MODULO CPU "UART_FIFO_MODULO"
201+
op sub UART_FIFO_CAPACITY UART_FIFO_MODULO 1
202202

203203
read uart0_rx_rptr UART0 254
204-
read uart0_rx_wptr UART0 255
204+
read uart0_rx_wptr_raw UART0 255
205205
read uart0_tx_rptr UART0 510
206206
read uart0_tx_wptr UART0 511
207207

208+
op and uart0_rx_wptr uart0_rx_wptr_raw 0xff
209+
208210
op sub uart0_rx_fifo_size uart0_rx_wptr uart0_rx_rptr
209211
op add uart0_rx_fifo_size uart0_rx_fifo_size UART_FIFO_MODULO
210212
op mod uart0_rx_fifo_size uart0_rx_fifo_size UART_FIFO_MODULO
@@ -213,11 +215,17 @@ loop:
213215
op add uart0_tx_fifo_size uart0_tx_fifo_size UART_FIFO_MODULO
214216
op mod uart0_tx_fifo_size uart0_tx_fifo_size UART_FIFO_MODULO
215217

218+
op equal uart0_rx_full uart0_rx_fifo_size UART_FIFO_CAPACITY
219+
op notEqual uart0_rx_overflow_flag uart0_rx_wptr uart0_rx_wptr_raw
220+
op land uart0_rx_overrun uart0_rx_full uart0_rx_overflow_flag
221+
216222
read uart1_rx_rptr UART1 254
217-
read uart1_rx_wptr UART1 255
223+
read uart1_rx_wptr_raw UART1 255
218224
read uart1_tx_rptr UART1 510
219225
read uart1_tx_wptr UART1 511
220226

227+
op and uart1_rx_wptr uart1_rx_wptr_raw 0xff
228+
221229
op sub uart1_rx_fifo_size uart1_rx_wptr uart1_rx_rptr
222230
op add uart1_rx_fifo_size uart1_rx_fifo_size UART_FIFO_MODULO
223231
op mod uart1_rx_fifo_size uart1_rx_fifo_size UART_FIFO_MODULO
@@ -226,6 +234,10 @@ loop:
226234
op add uart1_tx_fifo_size uart1_tx_fifo_size UART_FIFO_MODULO
227235
op mod uart1_tx_fifo_size uart1_tx_fifo_size UART_FIFO_MODULO
228236

237+
op equal uart1_rx_full uart1_rx_fifo_size UART_FIFO_CAPACITY
238+
op notEqual uart1_rx_overflow_flag uart1_rx_wptr uart1_rx_wptr_raw
239+
op land uart1_rx_overrun uart1_rx_full uart1_rx_overflow_flag
240+
229241
# markers
230242

231243
print "ROM_START\n{0}"
@@ -546,29 +558,33 @@ loop__no_mark_icache:
546558
format "machine"
547559
done_privilege_mode:
548560

549-
print "uart0_rx_fifo_size = {0}\n"
550-
jump uart0_rx_fifo_size__no_overflow lessThanEq uart0_rx_fifo_size UART_FIFO_CAPACITY
551-
format "{0} (!)"
552-
uart0_rx_fifo_size__no_overflow:
561+
print "uart0_rx_size = {0} ({1} -> {2})"
562+
jump uart0_rx_fifo_size__no_overrun notEqual uart0_rx_overrun true
563+
print " !!"
564+
uart0_rx_fifo_size__no_overrun:
553565
format uart0_rx_fifo_size
566+
format uart0_rx_rptr
567+
format uart0_rx_wptr
568+
print "\n"
554569

555-
print "uart0_tx_fifo_size = {0}\n\n"
556-
jump uart0_tx_fifo_size__no_overflow lessThanEq uart0_tx_fifo_size UART_FIFO_CAPACITY
557-
format "{0} (!)"
558-
uart0_tx_fifo_size__no_overflow:
570+
print "uart0_tx_size = {0} ({1} -> {2})\n\n"
559571
format uart0_tx_fifo_size
572+
format uart0_tx_rptr
573+
format uart0_tx_wptr
560574

561-
print "uart1_rx_fifo_size = {0}\n"
562-
jump uart1_rx_fifo_size__no_overflow lessThanEq uart1_rx_fifo_size UART_FIFO_CAPACITY
563-
format "{0} (!)"
564-
uart1_rx_fifo_size__no_overflow:
575+
print "uart1_rx_size = {0} ({1} -> {2})"
576+
jump uart1_rx_fifo_size__no_overrun notEqual uart1_rx_overrun true
577+
print " !!"
578+
uart1_rx_fifo_size__no_overrun:
565579
format uart1_rx_fifo_size
580+
format uart1_rx_rptr
581+
format uart1_rx_wptr
582+
print "\n"
566583

567-
print "uart1_tx_fifo_size = {0}\n"
568-
jump uart1_tx_fifo_size__no_overflow lessThanEq uart1_tx_fifo_size UART_FIFO_CAPACITY
569-
format "{0} (!)"
570-
uart1_tx_fifo_size__no_overflow:
584+
print "uart1_tx_size = {0} ({1} -> {2})\n"
571585
format uart1_tx_fifo_size
586+
format uart1_tx_rptr
587+
format uart1_tx_wptr
572588

573589
#{{'\n'}} {{ print(36, 17) }}
574590

src/init.mlog.jinja

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
#% extends 'main.constants.jinja'
22
#% block contents
33

4-
# this processor initializes INCR using a lookup table, sets readonly CSR values, and disables RAM procs to reduce lag
4+
# this processor performs the following one-time initialization steps:
5+
# - disable RAM procs to reduce lag
6+
# - initialize INCR using a lookup table
7+
# - set readonly CSR values
8+
# - reset all UART FIFO pointers to 0
9+
510
# the lookup table must be linked first
611

712
set LOOKUP_START 0
8-
set EXPECTED_LINKS 19
13+
set EXPECTED_LINKS 21
914

1015
# wait until ready
1116
reset:
@@ -17,6 +22,8 @@ reset:
1722

1823
set CSRS processor18
1924
set CPU processor19
25+
set UART0 bank1
26+
set UART1 bank2
2027

2128
op div wait {{LOOKUP_PROC_SIZE}} 120 # micro proc instructions/sec
2229
op ceil wait wait
@@ -102,6 +109,16 @@ init_incr:
102109
write 0 CSRS "{{ 'mscratch'|csr }}"
103110
write 0 CSRS "{{ 'mie'|csr }}"
104111

112+
# init UARTs
113+
write 0 UART0 {{UART_RX_RPTR}}
114+
write 0 UART0 {{UART_RX_WPTR}}
115+
write 0 UART0 {{UART_TX_RPTR}}
116+
write 0 UART0 {{UART_TX_WPTR}}
117+
write 0 UART1 {{UART_RX_RPTR}}
118+
write 0 UART1 {{UART_RX_WPTR}}
119+
write 0 UART1 {{UART_TX_RPTR}}
120+
write 0 UART1 {{UART_TX_WPTR}}
121+
105122
setrate 1
106123
stop
107124

@@ -126,4 +143,8 @@ lookup_variable:
126143
set {{LOOKUP_PROC_SIZE}} null
127144
set {{ROM_PROC_BYTES}} null
128145
set {{RAM_PROC_VARS}} null
146+
set {{UART_RX_RPTR}} null
147+
set {{UART_RX_WPTR}} null
148+
set {{UART_TX_RPTR}} null
149+
set {{UART_TX_WPTR}} null
129150
# {% endraw %}

src/kbconv.mlog

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
set COPYRIGHT "kbconv - a sortKB scancode decoder\n© 2025 camelStyleUser, BasedUser. Released under GPLv3 (https://www.gnu.org/licenses/gpl-3.0.html).\nThis program is provided without any warranty."
77
set SOURCELINK "https://github.com/BasedUser/mPC"
88

9-
read UART_CAPACITY processor1 "UART_FIFO_CAPACITY"
10-
op mul UART_MODULO UART_CAPACITY 2
9+
read UART_FIFO_MODULO processor1 "UART_FIFO_MODULO"
1110
read uart_wptr bank1 255
11+
op and uart_wptr uart_wptr 0xff # clear overflow flag if set by another producer
1212

1313
read modifiers cell2 0
1414
op and shift modifiers 1
@@ -59,22 +59,17 @@ jump handleSpecial greaterThan tgtmodifier 0 # but ONLY if there's anything to
5959
noApplySpecial: # TODO: once bank3 as scancode is in
6060
appliedSpecial:
6161

62-
wait_for_uart:
63-
read uart_rptr bank1 254
64-
65-
op sub uart_size uart_wptr uart_rptr
66-
op add uart_size uart_size UART_MODULO
67-
op mod uart_size uart_size UART_MODULO
62+
op add next_uart_wptr uart_wptr 1
63+
op mod next_uart_wptr next_uart_wptr UART_FIFO_MODULO
6864

6965
# wait for uart to have space
70-
jump wait_for_uart greaterThanEq uart_size UART_CAPACITY
66+
wait_for_uart:
67+
read uart_rptr bank1 254
68+
jump wait_for_uart equal uart_rptr next_uart_wptr
7169

7270
# uart has space, write a byte
73-
op mod i uart_wptr UART_CAPACITY
74-
write letter bank1 i
75-
76-
op add uart_wptr uart_wptr 1
77-
op mod uart_wptr uart_wptr UART_MODULO
71+
write letter bank1 uart_wptr
72+
set uart_wptr next_uart_wptr
7873
write uart_wptr bank1 255
7974
jump loop always
8075

0 commit comments

Comments
 (0)