Compare commits

...

3 Commits

Author SHA1 Message Date
Colin 2e649e2c86 Add softuart to soc_cxxrtl test. 2025-03-30 18:36:35 +08:00
Colin 464bd40440 Add softuart. 2025-03-30 16:23:29 +08:00
Colin d2942fe094 Add uart software lib. 2025-03-30 00:21:49 +08:00
7 changed files with 602 additions and 6 deletions

115
test/sim/common/uart.h Normal file
View File

@ -0,0 +1,115 @@
#ifndef _UART_H_
#define _UART_H_
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#define UART_BASE 0x40004000
#ifndef __ASSEMBLER__
#include <stdint.h>
#define DECL_REG(addr, name) \
volatile uint32_t *const(name) = (volatile uint32_t *)(addr)
#define __time_critical __attribute__((section(".time_critical")))
typedef volatile uint32_t io_rw_32;
#endif
// #include "addressmap.h"
#include "uart_regs.h"
DECL_REG(UART_BASE + UART_CSR_OFFS, UART_CSR);
DECL_REG(UART_BASE + UART_DIV_OFFS, UART_DIV);
DECL_REG(UART_BASE + UART_FSTAT_OFFS, UART_FSTAT);
DECL_REG(UART_BASE + UART_TX_OFFS, UART_TX);
DECL_REG(UART_BASE + UART_RX_OFFS, UART_RX);
static inline void uart_enable(bool en) {
*UART_CSR = *UART_CSR & ~UART_CSR_EN_MASK | (!!en << UART_CSR_EN_LSB);
}
// 10.4 fixed point format.
// Encodes number of clock cycles per clock enable.
// Each baud period is 16 clock enables (default modparam)
static inline void uart_clkdiv(uint32_t div) { *UART_DIV = div; }
// Use constant arguments:
#define uart_clkdiv_baud(clk_mhz, baud) \
uart_clkdiv((uint32_t)((clk_mhz) * 1e6 * (16.0 / 8.0) / (float)(baud)))
static inline bool uart_tx_full() {
return !!(*UART_FSTAT & UART_FSTAT_TXFULL_MASK);
}
static inline bool uart_tx_empty() {
return !!(*UART_FSTAT & UART_FSTAT_TXEMPTY_MASK);
}
static inline size_t uart_tx_level() {
return (*UART_FSTAT & UART_FSTAT_TXLEVEL_MASK) >> UART_FSTAT_TXLEVEL_LSB;
}
static inline bool uart_rx_full() {
return !!(*UART_FSTAT & UART_FSTAT_RXFULL_MASK);
}
static inline bool uart_rx_empty() {
return !!(*UART_FSTAT & UART_FSTAT_RXEMPTY_MASK);
}
static inline size_t uart_rx_level() {
return (*UART_FSTAT & UART_FSTAT_RXLEVEL_MASK) >> UART_FSTAT_RXLEVEL_LSB;
}
static inline void uart_put(uint8_t x) {
while (uart_tx_full());
*(volatile uint8_t *const)UART_TX = x;
}
static inline uint8_t uart_get() {
while (uart_rx_empty());
return *(volatile uint8_t *const)UART_RX;
}
static inline void uart_puts(const char *s) {
while (*s) {
if (*s == '\n') uart_put('\r');
uart_put((uint8_t)(*s++));
}
}
// Have you seen how big printf is?
static const char hextable[16] = {'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
static inline void uart_putint(uint32_t x) {
for (int i = 0; i < 8; ++i) {
uart_put((uint8_t)(hextable[x >> 28]));
x <<= 4;
}
}
static inline void uart_putbyte(uint8_t x) {
uart_put((uint8_t)(hextable[x >> 4]));
uart_put((uint8_t)(hextable[x & 0xf]));
}
static inline void uart_wait_done() { while (*UART_CSR & UART_CSR_BUSY_MASK); }
static inline void uart_init() {
*UART_CSR = 0;
while (*UART_CSR & UART_CSR_BUSY_MASK);
while (!uart_rx_empty()) (void)uart_get();
uart_enable(true);
}
static inline void uart_enable_cts(bool en) {
*UART_CSR = *UART_CSR & ~UART_CSR_CTSEN_MASK | (!!en << UART_CSR_CTSEN_LSB);
}
#endif // _UART_H_

142
test/sim/common/uart_regs.h Normal file
View File

@ -0,0 +1,142 @@
/*******************************************************************************
* AUTOGENERATED BY REGBLOCK *
* Do not edit manually. *
* Edit the source file (or regblock utility) and regenerate. *
*******************************************************************************/
#ifndef _UART_REGS_H_
#define _UART_REGS_H_
// Block name : uart
// Bus type : apb
// Bus data width : 32
// Bus address width : 16
#define UART_CSR_OFFS 0
#define UART_DIV_OFFS 4
#define UART_FSTAT_OFFS 8
#define UART_TX_OFFS 12
#define UART_RX_OFFS 16
/*******************************************************************************
* CSR *
*******************************************************************************/
// Control and status register
// Field: CSR_EN Access: RW
// UART runs when en is high. Synchronous reset (excluding FIFOs) when low.
#define UART_CSR_EN_LSB 0
#define UART_CSR_EN_BITS 1
#define UART_CSR_EN_MASK 0x1
// Field: CSR_BUSY Access: ROV
// UART TX is still sending data
#define UART_CSR_BUSY_LSB 1
#define UART_CSR_BUSY_BITS 1
#define UART_CSR_BUSY_MASK 0x2
// Field: CSR_TXIE Access: RW
// Enable TX FIFO interrupt
#define UART_CSR_TXIE_LSB 2
#define UART_CSR_TXIE_BITS 1
#define UART_CSR_TXIE_MASK 0x4
// Field: CSR_RXIE Access: RW
// Enable RX FIFO interrupt
#define UART_CSR_RXIE_LSB 3
#define UART_CSR_RXIE_BITS 1
#define UART_CSR_RXIE_MASK 0x8
// Field: CSR_CTSEN Access: RW
// Enable pausing of TX while CTS is not asserted
#define UART_CSR_CTSEN_LSB 4
#define UART_CSR_CTSEN_BITS 1
#define UART_CSR_CTSEN_MASK 0x10
// Field: CSR_LOOPBACK Access: RW
// Connect TX -> RX and RTS -> CTS internally (for testing).
#define UART_CSR_LOOPBACK_LSB 8
#define UART_CSR_LOOPBACK_BITS 1
#define UART_CSR_LOOPBACK_MASK 0x100
/*******************************************************************************
* DIV *
*******************************************************************************/
// Clock divider control fields
// Field: DIV_INT Access: WO
#define UART_DIV_INT_LSB 4
#define UART_DIV_INT_BITS 10
#define UART_DIV_INT_MASK 0x3ff0
// Field: DIV_FRAC Access: WO
#define UART_DIV_FRAC_LSB 0
#define UART_DIV_FRAC_BITS 4
#define UART_DIV_FRAC_MASK 0xf
/*******************************************************************************
* FSTAT *
*******************************************************************************/
// FIFO status register
// Field: FSTAT_TXLEVEL Access: ROV
#define UART_FSTAT_TXLEVEL_LSB 0
#define UART_FSTAT_TXLEVEL_BITS 8
#define UART_FSTAT_TXLEVEL_MASK 0xff
// Field: FSTAT_TXFULL Access: ROV
#define UART_FSTAT_TXFULL_LSB 8
#define UART_FSTAT_TXFULL_BITS 1
#define UART_FSTAT_TXFULL_MASK 0x100
// Field: FSTAT_TXEMPTY Access: ROV
#define UART_FSTAT_TXEMPTY_LSB 9
#define UART_FSTAT_TXEMPTY_BITS 1
#define UART_FSTAT_TXEMPTY_MASK 0x200
// Field: FSTAT_TXOVER Access: W1C
#define UART_FSTAT_TXOVER_LSB 10
#define UART_FSTAT_TXOVER_BITS 1
#define UART_FSTAT_TXOVER_MASK 0x400
// Field: FSTAT_TXUNDER Access: W1C
#define UART_FSTAT_TXUNDER_LSB 11
#define UART_FSTAT_TXUNDER_BITS 1
#define UART_FSTAT_TXUNDER_MASK 0x800
// Field: FSTAT_RXLEVEL Access: ROV
#define UART_FSTAT_RXLEVEL_LSB 16
#define UART_FSTAT_RXLEVEL_BITS 8
#define UART_FSTAT_RXLEVEL_MASK 0xff0000
// Field: FSTAT_RXFULL Access: ROV
#define UART_FSTAT_RXFULL_LSB 24
#define UART_FSTAT_RXFULL_BITS 1
#define UART_FSTAT_RXFULL_MASK 0x1000000
// Field: FSTAT_RXEMPTY Access: ROV
#define UART_FSTAT_RXEMPTY_LSB 25
#define UART_FSTAT_RXEMPTY_BITS 1
#define UART_FSTAT_RXEMPTY_MASK 0x2000000
// Field: FSTAT_RXOVER Access: W1C
#define UART_FSTAT_RXOVER_LSB 26
#define UART_FSTAT_RXOVER_BITS 1
#define UART_FSTAT_RXOVER_MASK 0x4000000
// Field: FSTAT_RXUNDER Access: W1C
#define UART_FSTAT_RXUNDER_LSB 27
#define UART_FSTAT_RXUNDER_BITS 1
#define UART_FSTAT_RXUNDER_MASK 0x8000000
/*******************************************************************************
* TX *
*******************************************************************************/
// TX data FIFO
// Field: TX Access: WF
#define UART_TX_LSB 0
#define UART_TX_BITS 8
#define UART_TX_MASK 0xff
/*******************************************************************************
* RX *
*******************************************************************************/
// RX data FIFO
// Field: RX Access: RF
#define UART_RX_LSB 0
#define UART_RX_BITS 8
#define UART_RX_MASK 0xff
#endif // _UART_REGS_H_

View File

@ -1,12 +1,23 @@
#include "tb_cxxrtl_io.h"
__attribute__((optimize("O0"))) int main() {
#include "uart.h"
#define CLK_FREQ_MHZ 12
// __attribute__((optimize("O0")))
int main() {
// Float stuff should all be compile-time. 115200 baud.
uart_init();
uart_clkdiv_baud(CLK_FREQ_MHZ, 9600);
uart_puts("Hello, UART!\n");
// Need to wait for completion, else we will terminate the simulation
// while characters still in FIFO.
uart_wait_done();
// tb_puts("Hello world from Hazard3 + CXXRTL!\n");
uint32_t addr = 0x40008000;
uint32_t *point = (uint32_t *)addr;
*point = 'C';
*point = 'O';
*point = 'L';
*point = 'I';
*point = 'N';
return 123;
}

View File

@ -11,6 +11,9 @@ TBEXEC := $(patsubst %.f,%,$(DOTF))
FILE_LIST := $(shell HDL=$(HDL) $(SCRIPTS)/listfiles $(DOTF))
BUILD_DIR := build-$(patsubst %.f,%,$(DOTF))
SRC = tb.cpp softuart.c
INC = -I ${BUILD_DIR} -I ./
# Note: clang++-18 has a >20x compile time regression, even at low
# optimisation levels. I have tried clang++-16 and clang++-17, both fine.
CLANGXX := clang++-16
@ -40,7 +43,7 @@ gdb:
/opt/riscv/bin/riscv32-unknown-elf-gdb -x gdb_init
$(TBEXEC): $(BUILD_DIR)/dut.cpp tb.cpp
$(CLANGXX) -O3 -std=c++14 $(addprefix -D,$(CDEFINES) $(CDEFINES_$(DOTF))) -I $(shell yosys-config --datdir)/include/backends/cxxrtl/runtime -I $(BUILD_DIR) tb.cpp -o $(TBEXEC)
$(CLANGXX) -O3 -std=c++14 $(addprefix -D,$(CDEFINES) $(CDEFINES_$(DOTF))) -I $(shell yosys-config --datdir)/include/backends/cxxrtl/runtime $(INC) ${SRC} -o $(TBEXEC)
lint:
verilator --lint-only --top-module $(TOP) -I$(HDL) $(FILE_LIST)

View File

@ -0,0 +1,242 @@
/*
Software Uart For Stm32
By Liyanboy74
https://github.com/liyanboy74
*/
#include "softuart.h"
// Some internal define
#if (SoftUart_PARITY)
#define SoftUart_IDEF_LEN_C1 (SoftUart_DATA_LEN + 2)
#else
#define SoftUart_IDEF_LEN_C1 (SoftUart_DATA_LEN + 1)
#endif
#define SoftUart_IDEF_LEN_C2 (SoftUart_IDEF_LEN_C1 + SoftUart_STOP_Bit)
// All Soft Uart Config and State
SoftUart_S SUart;
// TX RX Data Buffer
SoftUartBuffer_S SUBuffer;
// For timing division
uint8_t SU_Timer = 0;
// Parity var
static uint8_t DV, PCount;
// Initial Soft Uart
SoftUart_S *SoftUartInit() {
SUart.TxNComplated = 0;
SUart.RxBitCounter = 0;
SUart.RxBitShift = 0;
SUart.RxIndex = 0;
SUart.TxEnable = 0;
SUart.RxEnable = 0;
SUart.TxBitCounter = 0;
SUart.TxBitShift = 0;
SUart.TxIndex = 0;
SUart.TxSize = 0;
SUart.Buffer = &SUBuffer;
SUart.RxTimingFlag = 0;
SUart.RxBitOffset = 0;
return &SUart;
}
// Send one bit to TX pin
void SoftUartTransmitBit(SoftUart_S *SU, uint8_t Bit0_1) {
SU->TxPinValue = Bit0_1;
}
// Enable Soft Uart Receiving
SoftUartState_E SoftUartEnableRx() {
SUart.RxEnable = 1;
return SoftUart_OK;
}
// Disable Soft Uart Receiving
SoftUartState_E SoftUartDisableRx() {
SUart.RxEnable = 0;
return SoftUart_OK;
}
// Read Size of Received Data in buffer
uint8_t SoftUartRxAlavailable() { return SUart.RxIndex; }
// Move Received Data to Another Buffer
SoftUartState_E SoftUartReadRxBuffer(uint8_t *Buffer, uint8_t Len) {
int i;
for (i = 0; i < Len; i++) {
Buffer[i] = SUart.Buffer->Rx[i];
}
for (i = 0; i < SUart.RxIndex; i++) {
SUart.Buffer->Rx[i] = SUart.Buffer->Rx[i + Len];
}
SUart.RxIndex -= Len;
return SoftUart_OK;
}
// Soft Uart Transmit Data Process
void SoftUartTxProcess(SoftUart_S *SU) {
if (SU->TxEnable) {
// Start
if (SU->TxBitCounter == 0) {
SU->TxNComplated = 1;
SU->TxBitShift = 0;
SoftUartTransmitBit(SU, 0);
SU->TxBitCounter++;
PCount = 0;
}
// Data
else if (SU->TxBitCounter < (SoftUart_DATA_LEN + 1)) {
DV = ((SU->Buffer->Tx[SU->TxIndex]) >> (SU->TxBitShift)) & 0x01;
SoftUartTransmitBit(SU, DV);
SU->TxBitCounter++;
SU->TxBitShift++;
if (DV) PCount++;
}
// Parity
else if (SU->TxBitCounter < SoftUart_IDEF_LEN_C1) {
// Check Even or Odd
DV = PCount % 2;
// if Odd Parity
if (SoftUart_PARITY == 1) DV = !DV;
SoftUartTransmitBit(SU, DV);
SU->TxBitCounter++;
}
// Stop
else if (SU->TxBitCounter < SoftUart_IDEF_LEN_C2) {
SoftUartTransmitBit(SU, 1);
SU->TxBitCounter++;
}
// Complete
else if (SU->TxBitCounter == SoftUart_IDEF_LEN_C2) {
// Reset Bit Counter
SU->TxBitCounter = 0;
// Ready To Send Another Data
SU->TxIndex++;
// Check Size of Data
if (SU->TxSize > SU->TxIndex) {
// Continue Sending
SU->TxNComplated = 1;
SU->TxEnable = 1;
} else {
// Finish
SU->TxNComplated = 0;
SU->TxEnable = 0;
}
}
}
}
// Soft Uart Receive Data Process
void SoftUartRxDataBitProcess(SoftUart_S *SU, uint8_t B0_1) {
if (SU->RxEnable) {
// Start
if (SU->RxBitCounter == 0) {
// Start Bit is 0
if (B0_1) return;
SU->RxBitShift = 0;
SU->RxBitCounter++;
SU->Buffer->Rx[SU->RxIndex] = 0;
}
// Data
else if (SU->RxBitCounter < (SoftUart_DATA_LEN + 1)) {
SU->Buffer->Rx[SU->RxIndex] |= ((B0_1 & 0x01) << SU->RxBitShift);
SU->RxBitCounter++;
SU->RxBitShift++;
}
// Parity
else if (SU->RxBitCounter < SoftUart_IDEF_LEN_C1) {
// Need to be check
// B0_1;
SU->RxBitCounter++;
}
// Stop & Complete
else if (SU->RxBitCounter < SoftUart_IDEF_LEN_C2) {
SU->RxBitCounter = 0;
SU->RxTimingFlag = 0;
// Stop Bit must be 1
if (B0_1) {
// Received successfully
// Change RX Buffer Index
if ((SU->RxIndex) < (SoftUartRxBufferSize - 1)) (SU->RxIndex)++;
}
// if not : ERROR -> Overwrite data
}
}
}
// Wait Until Transmit Completed
// You do not usually need to use this function!
void SoftUartWaitUntilTxComplate() { while (SUart.TxNComplated); }
// Copy Data to Transmit Buffer and Start Sending
SoftUartState_E SoftUartPuts(uint8_t *Data, uint8_t Len) {
int i;
if (SUart.TxNComplated) return SoftUart_Error;
SUart.TxIndex = 0;
SUart.TxSize = Len;
for (i = 0; i < Len; i++) {
SUart.Buffer->Tx[i] = Data[i];
}
SUart.TxNComplated = 1;
SUart.TxEnable = 1;
return SoftUart_OK;
}
// Capture RX and Get BitOffset
uint8_t SoftUartScanRxPorts(SoftUart_S *SU) {
uint8_t Buffer = 0x00, Bit;
// Read RX GPIO Value
Bit = SU->RxPinValue;
// Starting conditions
if (!SUart.RxBitCounter && !SUart.RxTimingFlag && !Bit) {
// Save RX Bit Offset
// Calculate middle position of data puls
SUart.RxBitOffset = ((SU_Timer + 2) % 5);
// Timing Offset is Set
SUart.RxTimingFlag = 1;
}
// Add all RX GPIO State to Buffer
Buffer |= (Bit & 0x01);
return Buffer;
}
// SoftUartHandler must call in interrupt every 0.2*(1/BR)
// if BR=9600 then 0.2*(1/9600)=20.8333333 uS
void SoftUartHandler(void) {
uint8_t SU_DBuffer;
// Capture RX and Get BitOffset
SU_DBuffer = SoftUartScanRxPorts(&SUart);
// Receive Data if we in middle data pulse position
if (SUart.RxBitOffset == SU_Timer) {
SoftUartRxDataBitProcess(&SUart, (SU_DBuffer & 0x01));
}
// Sending always happens in the first time slot
if (SU_Timer == 0) {
// Transmit Data
SoftUartTxProcess(&SUart);
}
// Timing process
SU_Timer++;
if (SU_Timer >= 5) SU_Timer = 0;
}

View File

@ -0,0 +1,52 @@
/*
Software Uart For Stm32
By Liyanboy74
https://github.com/liyanboy74
*/
#include <cstdint>
#include <fstream>
#include <iostream>
#include <string>
#define Number_Of_SoftUarts 1 // Max 8
#define SoftUartTxBufferSize 32
#define SoftUartRxBufferSize 64
#define SoftUart_DATA_LEN 8 // Max 8 Bit
#define SoftUart_PARITY 0 // 0=None 1=odd 2=even
#define SoftUart_STOP_Bit 1 // Number of stop bits
typedef enum { SoftUart_OK, SoftUart_Error } SoftUartState_E;
typedef struct {
uint8_t Tx[SoftUartTxBufferSize];
uint8_t Rx[SoftUartRxBufferSize];
} SoftUartBuffer_S;
typedef struct {
uint8_t TxNComplated;
uint8_t TxEnable;
uint8_t RxEnable;
uint8_t TxBitShift, TxBitCounter;
uint8_t RxBitShift, RxBitCounter;
uint8_t TxIndex, TxSize;
uint8_t RxIndex;
SoftUartBuffer_S *Buffer;
uint8_t RxTimingFlag;
uint8_t RxBitOffset;
uint8_t RxPinValue; // set before SoftUartHandler
uint8_t TxPinValue; // get after SoftUartHandler
} SoftUart_S;
// Call Every (0.2)*(1/9600) = 20.83 uS
void SoftUartHandler(void);
void SoftUartWaitUntilTxComplate();
uint8_t SoftUartRxAlavailable();
SoftUartState_E SoftUartPuts(uint8_t *Data, uint8_t Len);
SoftUartState_E SoftUartEnableRx();
SoftUartState_E SoftUartDisableRx();
SoftUart_S *SoftUartInit();
SoftUartState_E SoftUartReadRxBuffer(uint8_t *Buffer, uint8_t Len);

View File

@ -12,6 +12,7 @@
#include <cxxrtl/cxxrtl_vcd.h>
#include "dut.cpp"
#include "softuart.h"
// There must be a better way
#ifdef __x86_64__
@ -110,6 +111,9 @@ int main(int argc, char **argv) {
bool replay_jtag = false;
std::string jtag_replay_path;
SoftUart_S *su = SoftUartInit();
SoftUartEnableRx();
for (int i = 1; i < argc; ++i) {
std::string s(argv[i]);
if (s.rfind("--", 0) != 0) {
@ -246,6 +250,8 @@ int main(int argc, char **argv) {
top.step();
top.step(); // workaround for github.com/YosysHQ/yosys/issues/2780
int uart_sample_count = 0;
bool timed_out = false;
for (int64_t cycle = 0; cycle < max_cycles || max_cycles == 0; ++cycle) {
top.p_clk.set<bool>(false);
@ -335,6 +341,31 @@ int main(int argc, char **argv) {
}
}
// 12Mhz -> 9600 = 1250
// SoftUartHandler must call in interrupt every 0.2*(1/BR)
if (uart_sample_count >= (1250 / 5)) {
// if (top.p_uart__tx.get<bool>())
// printf("1");
// else
// printf("0");
su->RxPinValue = (top.p_uart__tx.get<bool>() ? 1 : 0);
SoftUartHandler();
// uint8_t tx = su->TxPinValue;
// uint8_t ch = getchar(0);
// SoftUartPuts(0, &ch, 1);
if (SoftUartRxAlavailable()) {
uint8_t ch = 0;
auto re = SoftUartReadRxBuffer(&ch, 1);
if (re == SoftUart_OK && ch) printf("%c", ch);
}
uart_sample_count = 0;
} else {
uart_sample_count++;
}
if (dump_waves) {
// The extra step() is just here to get the bus responses to line up
// nicely in the VCD (hopefully is a quick update)