Verilog RS232 Synch-UART & RS232 Debugger source code and educational tutorial. - Page 1
Forum section: electronics / general

author: BrianHG
author_rank: Super Contributor
timestamp: November 26, 2019, 01:46:25 am
content: GitHub repository added.https://github.com/BrianHGinc/Verilog-RS232-Synch-UART-RS232-Debugger-and-PC-host-RS232-Hex-editorOk, I've worked on this utility over the past 2 weeks for use in the 'FPGA VGA Controller for 8-bit computer' thread. I wanted a means to access/peek into a FPGA project which had a memory bus. I designed a Verilog core to do this and since I was really helping out in the thread, and this core plus PC host HEX viewer/editor works really well, I figured I would document the source code really well and provide it as an example & tutorial material.There are 3 pieces:1) The ' SYNC_RS232_UART.v ' : A synchronizing RS232 transceiver. The 'synchronizing' transmitter with the receiver is crucial when interfacing with a PC as high speed full duplex communication require that the bit and word timing of serial data coming into the PC matches the clock and phase of the data the PC may be transmitting out at the same time. Many simple example Verilog RS232 UARTs arent capable of this. Read attached source code and see attached illustration to show you an example setup where my transmitter aligns itself to a received RS232 serial signal with a slightly different baud rate.Get full code in attached .v.txt file.2) The ' RS232_Debugger.v ' : A Verilog module which uses the SYNC_RS232_UART.v, has an RS232 RXD input and TXD output. It also has a memory address output and 8 bit memory data input and output with a memory read request and memory write enable. This module also provides a reset output, 4 utility 8 bit output ports and 4 utility 8 bit input ports. Once again, everything is documented in the available 'RS232_Debugger.v' source code and I'm here to answer questions.Get full code in attached .v.txt file.3) The ' RS232_Debugger.exe ' Viewer / HEX editor software. (For now, I haven't included the source code for this one, give me a week to clean it up first, though, it is fully functional.) The software offers real-time viewing and editing of the user configured memory size set in the RS232_Debugger.v's memory address size parameter while also continuously displaying the values of the 4 utility 8 bit input ports as well as being able to set the values of the 4 utility 8 bit output ports. When the RS232 com port is closed, the RS232_Debugger.exe functions as a stand alone HEX / ASCII file editor which can edit up to 1 megabyte files.The Verilog code was written using pure synchronous logic. No fancy async-reset/presets, no S/R, J-K flipflops. These examples should easily integrate into any vendor's FPGAs EDA tool platform. If anyone out there has success, let us know.Update: Get version 1.1 of ' RS232_Debugger.v ' here:RS232_Debugger.v Ver 1.1Update: Get versions 1.2 which now work in ModelSim here:RS232_UART & RS232_Debugger V1.2Get latest hex editor here:RS232 Hex Editor V1.3, Jan 14, 2022

author: BrianHG
author_rank: Super Contributor
timestamp: November 26, 2019, 01:48:04 am
content: This is the source for the 'SYNC_RS232_UART.v':Code:// *****************************************************************// *** SYNC_RS232_UART.v V1.0, November 22, 2019// ***// *** This transceiver follows slight baud timing errors introduced// *** by the external host interface's clock making the TDX output// *** clock timing synchronize to the RXD coming in allowing// *** high speed synchronous communications. A requirement// *** for PC RS232 full duplex synchronous communications.// ***// *** Written by Brian Guralnick.// *** Using generic Verilog code which only uses synchronous logic.// *** Well commented for educational purposes.// *****************************************************************module sync_rs232_uart (input wire clk, // System clockinput wire rxd, // RS232 serial input data pinoutput reg rx_rdy, // Pulsed high for 1 system clock when the received 8 bit data byte is readyoutput reg [7:0] rx_data, // Received 8 bit data byte.input wire ena_tx, // Signals the transmitter to latch the 8 bit data and begin transmittinginput wire [7:0] tx_data, // 8 bit data byte input to be transmittedoutput reg txd, // RS232 serial output data pinoutput reg tx_busy, // High when transmitter is busy. Low when you may load a byte to transmitoutput reg rx_sample_pulse // For debugging RXD errors only. This is an output test pulse// aligned to when the receiver has sampled the RXD input.) ;// Setup parametersparameter CLK_IN_HZ = 50000000; // Set to system input clock frequencyparameter BAUD_RATE = 921600; // Set to desired baud ratelocalparam RX_PERIOD = (CLK_IN_HZ / BAUD_RATE) -1 ; // Set's a reference counter size for each transmitted/received serial data bitlocalparam TX_PERIOD = (CLK_IN_HZ / BAUD_RATE) -1 ;// Receiver regsreg [15:0] rx_period ;reg [3:0] rx_position ;reg [9:0] rx_byte ;reg rxd_reg, last_rxd ;regrx_busy, rx_last_busy ;// Transmitter regsreg [15:0] tx_period = 16'h0 ;reg [3:0] tx_position = 4'h0 ;reg [9:0] tx_byte = 10'b1111111111 ;reg [7:0] tx_data_reg = 8'b11111111 ;reg tx_run = 1'b0 ;//********************************************************************************************// make the rx_trigger 'WIRE' equal to any new RXD input High to Low transition (IE start bit)// when the receiver is not busy receiving a byte//********************************************************************************************wire rx_trigger ;assign rx_trigger = ( ~rxd_reg && last_rxd && ~rx_busy );always @ (posedge clk) begin//********************************// Receiver functions.//********************************// register clock the UART RDX input signal.// This is a personal preference as I prefer FPGA inputs which don't directly feed combinational logicrxd_reg <= rxd;last_rxd <= rxd_reg; // create a 1 clock delay register of the rxd_reg serial bitrx_last_busy <= rx_busy; // create a 1 clock delay of the rx_busy resister.rx_rdy <= rx_last_busy && ~rx_busy; // create the rx_rdy out pulse for 1 single clock when the rx_busy flag has gone low signifying that rx_data is readyif ( rx_trigger )begin // if a 'rx_trigger' event has taken placerx_period <= ( RX_PERIOD[15:0] >> 1 ) ; // set the period clock to half way inside a serial bit. This makes the best time to sample incoming// serial bits as the source baud rate may be slightly slow or fast maintaining a good data capture window all the way until the stop bitrx_busy <= 1'd1 ; // set the rx_busy flag to signify operation of the UART serial receiverrx_position <= 4'h9 ; // set the serial bit counter to position 9end else beginif ( rx_period==0 ) begin// if the receiver period counter has reached it's endrx_period<= RX_PERIOD[15:0] ; // reset the period counterrx_sample_pulse <= rx_busy ; // *** This is only a test pulse for debugging purposesif ( rx_position != 0 ) begin // if the receiver's bit position counter hasn't reached it's endrx_position <= rx_position - 1'd1 ; // decrement the position counterrx_byte <= rxd_reg ; // load the receiver's serial shift regitser with the RXD input pinrx_byte[8:0] <= rx_byte[9:1] ; // shift the input serial shift register.end else begin // if the receiver's bit position counter reached 0rx_data <= rx_byte[9:2]; // load the output data register with the correct 8 bit contents of the serial input registerrx_busy <= 1'b0; // turn off the serial receiver busy flagendend else begin // if the receiver period counter has not reached it's endrx_period <= rx_period - 1'b1; // just decrement the receiver period counterrx_sample_pulse <= 1'b0 ; // *** This is only a test pulse for debugging purposesendend // ~rx_trigger//***********************************************************// SYNCHRONOUS! Transmitter functions// This was the most puzzling to get just right// So that both high and low speed intermittent// and continuous COM transactions would never// cause a byte error when communicating with// a PC as fast as possible.//***********************************************************if (ena_tx) begin // If a transmit request comes intx_data_reg <= tx_data ; // register a copy of the input data bustx_busy <= 1 ; // Set the busy flagend// ***********************************************************************************************************************// This section prepares the data, controls and shift register during the middle of the previous transmission bit.// ***********************************************************************************************************************if ( tx_period == (TX_PERIOD[15:0] >> 1) ) begin // ******* at the center of a serial transmitter bit ********if ( tx_position==1 ) begin // during the transmission of a stop bittx_run <= 0 ; // turn off the transmitter running flag. This point is the beginning of when// a synchronous transmit word alignment to an incomming RXD rx_trigger is permittedif (tx_busy) begin // before the next start bit, if the busy flag was set,tx_byte[8:1] <= tx_data_reg[7:0] ; // load the register copy of the tx_data_reg into the serial shift registertx_byte <= 1'b1 ; // Add a stop bit into the shift register's 10th bittx_byte <= 1'b0 ; // Add a start bit into the serial shift register's first bittx_busy <= 1'b0 ; // Turn off the busy flag signifying that another transmit byte may be loadedend// into the tx_data_regend else begintx_byte[8:0] <= tx_byte[9:1] ; // at any other point than the stop-bit period, shift the serial tx_byte shift registertx_byte <= 1'b1 ; // load a default stop bit into bit 10 of the serial shift registerif ( tx_position == 0 ) tx_run <= ~txd ; // during the 'center of a serial 'START' transmitter bit'// if the serial UART TXD output pin has a start bit, turn on the transmitter running flag// which signifies the point where it is no longer permit-able to align a transmit word// to an incoming RXD byte potentially corrupting a serial transmission.endend// ***********************************************************************************************************************// This section takes the above prepared registers and sends them out during the transition edge of the tx_period clock// and during inactivity, or during the permitted alignment window, it will re-align the transmission period clock to// a potential incoming rx_trigger event.// ***********************************************************************************************************************// if a RXD start bit transition edge is detected and the transmitter is not running,// IE during the safe synchronous transmit word alignment period// set halfway between the center of transmitting the stop bit and next start bitif ( rx_trigger && ~tx_run ) begintx_period <= TX_PERIOD[15:0] - 2'h2 ; // reset "SYNCHRONIZE" the transmit period timer to the rx_trigger event, recognizing that the rx_trigger is// delayed by 2 clocks, so we shave off 2 additional clock cycles for dead perfect parallel TXD output alignment.tx_position <= 1'b0 ; // force set the transmit reference position to the start bittxd <= tx_byte ; // immediately set the UART TXD output to the serial out shift register's start bit. IE see above if(tx_busy)end else if ( tx_period==0 )begin // if the transmitter period counter has reached it's endtx_period <= TX_PERIOD[15:0] ; // reset the period countertxd <= tx_byte ; // set the UART TXD output to the serial shift register's output.if ( tx_position == 0 ) tx_position <= 4'h9 ; // if the transmitter reference bit position counter is at the start bit, set it to bit 1.else tx_position <= tx_position - 1'b1 ; // otherwise, count down the position counter towards the stop bitend else tx_period <= tx_period - 1'b1 ; // if the transmit period has not reached it's end, it should count down.end // alwaysendmodule

author: BrianHG
author_rank: Super Contributor
timestamp: November 26, 2019, 01:49:05 am
content: Get version 1.1 here:RS232_Debugger.v Ver 1.1This is the source for the 'rs232_DEBUGGER.v' version 1.0:Code:// *********************************************************************************// *** RS232_Debugger.v Ver 1.0. November 22, 2019// ***// *** This RS232_Debugger.v allows a PC to access, real-time// *** display & edit up to 16 megabytes of addressable memory.// *** (For PCs with a 921600 baud limit, 1 megabyte max memory// *** recommended as it takes 14 seconds to transfer that entire// *** block of memory. This improves with faster com ports as the FPGA// *** can easily achieve more than 10 megabaud with this Verilog code.)// ***// *** This RS232_Debugger.v can also generate a reset signal sent from the// *** PC control software and it also has 4 utility 8 bit input ports which// *** are continuously monitored and displayed in real-time. It also has 4// *** utility 8 bit output ports which can be set to any value at any time.// ***// *** In a minimum configuration, this module uses 370 logic cells + the required// *** SYNC_RS232_UART.v uses 107 logic cells for a total of 477 logic cells. The// *** total increases to 570 logic cells when every feature and all 24 address// *** bits are being used.// ***// *** Written by and (C) Brian Guralnick.// *** Using generic Verilog code which only uses synchronous logic.// *** Well commented and intended for educational purposes.// ***// *********************************************************************************module rs232_debugger (input wire clk, // System clock. Recommend at least 20MHz for the 921600 baud rate.// This module is capable of over 100MHz on even the slowest FPGAs.output reg cmd_rst, // When sent by the PC RS232_Debugger utility, this outputs a high signal for 8 clock cycles.// It also runs high for 8 clock cycles during power-up.input wire rxd, // Connect this to the RS232 RXD input pin.output wire txd, // Connect this to the RS232 TXD output pin.output reg LED_txd, // Optionally wire this to a LED, it will go high whenever the RS232 TXD is active.output reg LED_rxd, // Optionally wire this to a LED, it will go high whenever the RS232 RXD is active.output reg host_rd_req, // This output will pulse high for 1 clock when a read request is taking place.input wire host_rd_rdy, // This input should be set high once the 'host_rdata[7:0]' input contains valid data.// Tie this input high if your read data will be always valid within 12 clock cycles since the hosr_rd_req.output reg host_wr_ena, // This output will pulse high for 1 clock when a write request is taking place.output wire [ADDR_SIZE-1:0] host_addr, // This output contains the requested read and write address.output reg [7:0] host_wdata, // This output contains the source RS232 8bit data to be written.input wire [7:0] host_rdata, // This input receives the 8 bit ram data to be sent to the RS232.// If 'host_rd_rdy' is tied to '1' the data needs to be valid within 12 clocks of the 'host_rd_req' pulse.// These are 4 8 bit utility input ports which are continuously read and displayed in the RS232_Debugger utility.input wire [7:0] in0,input wire [7:0] in1,input wire [7:0] in2,input wire [7:0] in3,// These are 4 8 bit utility output ports which are set by the RS232_Debugger utility.output reg [7:0] out0,output reg [7:0] out1,output reg [7:0] out2,output reg [7:0] out3);reg [7:0] in_reg0,in_reg1,in_reg2,in_reg3; // These 'in_reg#[7:0]' will be used to latch all 4 'in#[7:0]' inputs in parallel before transmitting their state through the RS232 transceiver.parameter CLK_IN_HZ = 50000000; // Set this parameter to your clock system clock frequency in Hertzparameter BAUD_RATE = 921600; // Keep this parameter at 921600 for the commanding RS232_Debugger PC software.parameter ADDR_SIZE = 20; // This sets the address size for the memory access. 24 is maximum, but unrealistic// at 921600 baud as it would take almost 4 minutes to transfer everything. 20 address bits,// 1048576 bytes / 1 megabyte takes around 14 seconds to transfer with the built in overhead.// For those who can access faster RS232 ports, these larger memory sizes could become more viable.localparam CLK_1KHz_PERIOD = CLK_IN_HZ / 1000 ; // The counter period for generating an internal 1 KHz timerlocalparam DEBUG_WDT_TIME = 4'd15; // Com activity watch dog timer. Time until a incomming Write to ram command is aborted due to inactive com.localparam LED_HOLD_TIME = 4'd15; // Keep the LED_rxd/txd signal high for at least this amount of ms time during RXD/TXD transactions.reg [17:0] tick_1KHz_counter ; // This reg is the counter for the main clock input which is used to generate the 1KHz timer.reg tick_1KHz ; // This reg will pulse for 1 'clk' cycle at 1KHz.reg [4:0] timeout_cnt;// This is a counter which is used for the communications watch dog timer which times out at the 'DEBUG_WDT' parameter count running at 1KHz speed.reg [4:0] led_txd_timeout, led_rxd_timeout; // These are timer counters used to keep the communication status LEDs on for the 'LED_HOLD_TIME' parameter count running at 1KHz speed.// *****************************************************************************************************************************************************************************// **** Example command structure shown in RS232 received and transmitted hex bytes:// ****// **** Example #1: CMD_READ_BYTES (See figure #1)// **** << SETUP HEADER >> <<CMD_PREFIX> <ADDR_POINT><TRANSFER_SIZE>> <<CMD_PREFIX> <ADDR_POINT><TRANSFER_SIZE>>// **** Read host ram string ( 80 FF FF 00 00 00 00 00 00 52 65 61 64 00 01 00 04 52 65 61 64 00 01 00 04 ) Both copies must match for// **** The last 16 characters RX_Buffer string << Copy #1 of command >> << Copy #2 of command >> the command to be accepted.// ****// **** RX_Buffer #7,6,5,4= 32'h 52 65 61 64 = CMD_READ_BYTES// **** RX_Buffer #3,2,1= 24'h 00 01 00 = From Address 24'h000100// **** RX_Buffer #0 = 8'h 04 = Transfer 8'h04 + 1 = 5 bytes.// ****// **** The RS232_Debugger will then transmit 5 bytes read from host ram port.// **** Example #2: CMD_READ_BURST// **** << SETUP HEADER >> <<CMD_PREFIX> <ADDR_POINT><TRANSFER_SIZE>> <<CMD_PREFIX> <ADDR_POINT><TRANSFER_SIZE>>// **** Read host ram string ( 80 FF FF 00 00 00 00 00 00 52 65 61 50 00 80 00 0F 52 65 61 50 00 80 00 0F )// **** The last 16 characters RX_Buffer string << Copy #1 of command >> << Copy #2 of command >>// ****// **** RX_Buffer #7,6,5,4= 32'h 52 65 61 50 = CMD_READ_BURST// **** RX_Buffer #3,2,1= 24'h 00 80 00 = From Address 24'h008000// **** RX_Buffer #0 = 8'h 0F = Transfer (8'h0F + 1) *256 = 4096 bytes.// ****// **** The RS232_Debugger will then transmit 4096 bytes read from host ram port.// ****// **** Example #3: CMD_WRITE_BYTES (See figure #2)// **** << SETUP HEADER >> <<CMD_PREFIX> <ADDR_POINT><TRANSFER_SIZE>> <<CMD_PREFIX> <ADDR_POINT><TRANSFER_SIZE>>// **** Read host ram string ( 80 FF FF 00 00 00 00 00 00 57 72 69 74 00 01 00 04 57 72 69 74 00 01 00 04 )// **** The last 16 characters RX_Buffer string << Copy #1 of command >> << Copy #2 of command >>// ****// **** RX_Buffer #7,6,5,4= 32'h 57 72 69 74 = CMD_WRITE_BYTES// **** RX_Buffer #3,2,1= 24'h 00 01 00 = To Address 24'h000100// **** RX_Buffer #0 = 8'h 04 = Transfer 8'h7F + 1 = 5 bytes.// ****// **** The RS232_Debugger will now expect to receive 5 bytes which will be written// **** into the host ram. The 5 received characters will be echoed back as verification.// **** If there is a pause or delay for more than 0.1 seconds, write command halts/aborts.// ****// **** Example #4: CMD_SET_PORTS (See figure #3)// **** << SETUP HEADER >> <<CMD_PREFIX> <Out0><Out1><Out2><Out3>> <<CMD_PREFIX> <Out0><Out1><Out2><Out3>>// **** Read host ram string ( 80 FF FF 00 00 00 00 00 00 53 65 74 50 AA BB CC DD 53 65 74 50 AA BB CC DD )// **** The last 16 characters RX_Buffer string << Copy #1 of command >> << Copy #2 of command >>// ****// **** RX_Buffer #7,6,5,4= 32'h 53 65 74 50 = CMD_SET_PORTS// **** RX_Buffer #3 = 8'h AA = Output port Out0[7:0] will be set to 8'hAA// **** RX_Buffer #2 = 8'h BB = Output port Out1[7:0] will be set to 8'hBB// **** RX_Buffer #1 = 8'h CC = Output port Out2[7:0] will be set to 8'hCC// **** RX_Buffer #0 = 8'h DD = Output port Out3[7:0] will be set to 8'hDD// ****// **** The RS232_Debugger will then transmit back the values of:// **** Ports In0[7:0], In1[7:0], In2[7:0], In3[7:0], then Parameter 'ADDR_SIZE[7:0]'.// ****// ****// *****************************************************************************************************************************************************************************wire [31:0] CMD_READ_BYTES, CMD_READ_BURST, CMD_WRITE_BYTES, CMD_WRITE_BURST, CMD_RESET, CMD_SET_PORTS; // Set wire labels for all the command strings.reg [15*8+7:0] RX_buffer; // This 16 word * 8 bit character register will take in the RS232 data as an 8 bit word pipe, 16 characters long.wire [31:0] CMD_PREFIX ; // Define a 32 bit wire bus in the RX_buffer which contains the command prefix and CMD_SUFFIXwire [23:0] CMD_ADDRESS_POINTER ; // Define a 24 bit wire bus for the 'CMD_ADDRESS_POINTER'wire [7:0] CMD_TRANSFER_SIZE ; // Define an 8 bit wire bus for the 'CMD_TRANSFER_SIZE'reg[3:0] RXD_00_cnt; // This register will count the consecutive number of bytes = 8'h00 ahead of the command. It will reset to 0 if any other byte value is received.reg[3:0] RXD_FF_cnt; // This register will count the number of bytes = 8'hFF ahead of the command. It will reset to 0 if any other byte value other than 8'h00 is received.// Assign values to all the command wireswire CMD_HEADER, CMD_VERIFY;assign CMD_HEADER = ( RXD_FF_cnt==4'h2 && RXD_00_cnt==4'h6 ); // Two consecutive 8'hFF and then 6 consecutive 8'h00 must be transmitted// as a header before a command, otherwise, all potential received commands will be ignored.assign CMD_VERIFY = ( RX_buffer[15*8+7:8*8] == RX_buffer[7*8+7:0*8] ); // To verify authenticate an incoming command, the 2 consecutive identical copies// of the 8 byte command must matchassign CMD_PREFIX = RX_buffer[7*8+7:4*8] ; // The point in the receive buffer is where the 4 character command is locatedassign CMD_READ_BYTES = 32'h52656164 ; // Read host ram and transmit to RS232, from 1 through 256 bytes.assign CMD_READ_BURST = 32'h52656150 ; // Page read host ram and transmit to RS232, from 256 through 65536 bytes.assign CMD_WRITE_BYTES = 32'h57726974 ; // Read RS232 data and write into host ram, from 1 through 256 bytes.assign CMD_WRITE_BURST = 32'h57726950 ; // Page Read RS232 data and write into host ram, from 256 through 65536 bytes.assign CMD_RESET = 32'h52657365 ; // Cycle the reset output command prefixassign CMD_SET_PORTS = 32'h53657450 ; // Set the general purpose output ports and read general purpose input ports + then transmit 'ADDR_SIZE' parameter.assign CMD_ADDRESS_POINTER = RX_buffer[3*8+7:1*8] ; // Point to the 3 8 bit words in the command's register which contains the 24 bit starting read and write addressassign CMD_TRANSFER_SIZE = RX_buffer[0*8+7:0*8] ; // Point to the 8 bit word in the command's register which contains the number of bytes+1 to transfer// Or in the case of a R/W_BURST, set the byte transfer quantity to (CMD_TRANSFER_SIZE+1) * 256reg [16:0] byte_count; // This register will be used to count the number of bytes which were requested by 'CMD_TRANSFER_SIZE' to be received or transmitted through the RS232 transceiver.reg [23:0] host_addr_reg ; // This register will hold and count the host read and write address.reg [7:0] host_rdata_reg ;// when the 'host_rd_rdy' input goes high, this register will latch the 'host_rdata' input port.assign host_addr[ADDR_SIZE-1:0] = host_addr_reg[ADDR_SIZE-1:0]; // Assign the host register output port the the host_addr_reg register counterreg [1:0] Function ; // This register holds which of the 4 possible program functions the RS232_Debugger is running.wire [1:0] FUNC_WAIT, FUNC_READ, FUNC_WRITE, FUNC_SET_PORTS ;assign FUNC_WAIT = 2'h0 ; // This function state waits for incoming commands. When a valid command is received, it will setup the next function state.assign FUNC_READ = 2'h1 ; // This function state will read 'byte_count+1' bytes of host ram and transmit the contents to the RS232 port's TXD.assign FUNC_WRITE = 2'h2 ; // This function state will read 'byte_count+1' bytes from the RS232 port's RXD and send the data to the host ram + send a copy back to the RS232 port's TXD.assign FUNC_SET_PORTS = 2'h3 ; // This function state will send the the 4 in#[7:0] ports' values and then the 'ADDR_SIZE' parameter through the RS232 transceiver.reg [3:0] rst_clk; // This will be used as a counter to pulse out the 'cmd_rst' output pin for 8 clock cycles when commanded to by the RS232.reg [3:0] tx_cyc; // This counter will be used to slow down and sequence actions when transmitting bytes out through the RS232 transceiver.// *************************************************************************// *** SYNC_RS232_UART.v setup. Read the SYNC_RS232_UART.v file to see how// *** I engineered this module and how I got the transmitter to function// *** synchronously with a PC's serial incoming RXD transmission.// ***// *** Timing diagrams on EEVBlog forum here:// ***// *************************************************************************wire uart_tx_full;wire rxd_rdy;reg [4:0] ena_rxd_dly; // This register will receive the UART's 'rxd_rdy' pulse and serial shift that pulse along it's 5 bits.wire [7:0] uart_rbyte;reg uart_tx;reg [7:0] uart_tbyte; // This reg will hold the byte which is about to be transmittedsync_rs232_uart rs232_io (.clk(clk),.rxd(rxd), // Goes to RXD Input Pin.txd(txd), // Goes to TXD output pin.rx_data(uart_rbyte), // Received data byte.rx_rdy(rxd_rdy), // 1 clock pulse high when the received data bit is ready.ena_tx(uart_tx), // Pulsed high for 1 clock when tx_data byte is ready to be sent.tx_data(uart_tbyte), // The byte which will be transmitted.tx_busy(uart_tx_full) ); // High when the 1 word FIFO in the UART's transmit buffer is fulldefparamrs232_io.CLK_IN_HZ = CLK_IN_HZ,rs232_io.BAUD_RATE = BAUD_RATE;always @ (posedge clk) begin// ******************************************************************// ****** Generate a generic 1 KHz timer tick pulse.// ******************************************************************if ( tick_1KHz_counter <= 18'h1 ) begintick_1KHz_counter <= CLK_1KHz_PERIOD[17:0];tick_1KHz <= 1'b1 ;end else begintick_1KHz_counter <= tick_1KHz_counter - 1'b1 ;tick_1KHz <= 1'b0 ;end// ******************************************************************// ****** Generate a status activity RXD and TXD led driver output.// ****** This routine keeps the LED outputs on long enough to// ****** visibly see as the data bursts are too short to be seen.// ******************************************************************if (uart_tx_full) beginled_txd_timeout <= LED_HOLD_TIME ;LED_txd <= 1'b1 ;end else if ( led_txd_timeout!=5'h0 && tick_1KHz ) led_txd_timeout <= led_txd_timeout - 1'b1 ;else if ( led_txd_timeout==5'h0 ) LED_txd <= 1'b0 ;if (rxd_rdy) beginled_rxd_timeout <= LED_HOLD_TIME ;LED_rxd <= 1'b1 ;end else if ( led_rxd_timeout!=5'h0 && tick_1KHz ) led_rxd_timeout <= led_rxd_timeout - 1'b1 ;else if ( led_rxd_timeout==5'h0 ) LED_rxd <= 1'b0 ;// ******************************************************************// ******************************************************************cmd_rst <= ~rst_clk ; // Register delay and invert latch bit 4 of the reset counter.if (~rst_clk) begin // *** Generate an 8 clock wide reset pulserst_clk <= rst_clk + 1'b1 ; // Count until bit 3 on the counter goes high.end else beginif (cmd_rst) begin // Last single 1 shot reset from the reset output signal 'cmd_rst'.host_addr_reg <= 24'h0 ;host_wr_ena <= 1'b0 ; // make sure we aren't writing ram.host_rd_req <= 1'b0 ; // make sure we aren't requesting a read from memoryena_rxd_dly[4:0] <= 5'h0 ; // clear out any possible RS232 transceiver rx_rdyRX_buffer[15*8+7:0] <= 128'h0 ; // clear out the entire 16 word by 8 bit character input command buffer.Function <= FUNC_WAIT ; // set the program state to the 'wait for incoming command' function.uart_tx<= 1'b0 ; // make sure no transmit character command is being sent to the RS232 transceiver.end else begin// ******************************************************************************************// *** setup a sequential accessible delayed pipe of the RS232 transceiver's 'rx_rdy' signal.// ******************************************************************************************ena_rxd_dly <= rxd_rdy ;ena_rxd_dly[4:1] <= ena_rxd_dly[3:0] ;// ******************************************************************************************************************// *** setup case statement for the 4 possible functions, FUNC_WAIT, FUNC_READ, FUNC_WRITE, FUNC_SET_PORTS// ******************************************************************************************************************case (Function)// ************************************************************************************************************************************************************************************// *** Beginning Function FUNC_WAIT. This function state waits for incoming commands. When a valid command is received, it will setup the next function state.// ************************************************************************************************************************************************************************************FUNC_WAIT : beginhost_rd_req <= 1'b0; // force off any ram transactions.host_wr_ena <= 1'b0;if (ena_rxd_dly) begin // Note we are using the ena_rxd_dly deliberately since if the 'FUNC_WRITE' is called, it uses a pre-setup time by triggering sequenced actions on earlier ena_rxd_dly[#]sRX_buffer[15*8+7:0] <= { RX_buffer[14*8+7:0] , uart_rbyte } ; // This will shift 1 byte at a time through the 16 character command bufferif ( RX_buffer[15*8+7:15*8]==8'h00 )RXD_00_cnt <= RXD_00_cnt + (RXD_00_cnt!=3'h7); // test the last byte in the 16 character buffer andelseRXD_00_cnt <= 3'h0 ; // count the number of bytes which are sequentially = 8'h00.if ( RX_buffer[15*8+7:15*8]==8'hFF )RXD_FF_cnt <= RXD_FF_cnt + (RXD_FF_cnt!=3'h7); // test the last byte in the 16 character buffer andelse if ( RX_buffer[15*8+7:15*8]!=8'h00 )RXD_FF_cnt <= 3'h0 ; // count the number of bytes which are sequentially = 8'hFFendif ( CMD_HEADER && CMD_VERIFY ) begin // Test to see if the command prefix string meets the 2 bytes of 8'hFF,// then 6 bytes of 8'h00, and then 2 command string of 8 bytes each which are identical// ********************************************************************************************************// *** setup case statement which will perform actions based on the 6 possible incoming CMD_PREFIX :// *** CMD_READ_BYTES, CMD_READ_BURST, CMD_WRITE_BYTES, CMD_WRITE_BURST, CMD_RESET, CMD_SET_PORTS// ********************************************************************************************************case (CMD_PREFIX)// ********************************************************************************************************// *** CMD_PREFIX case CMD_READ_BYTES. Read host ram and transmit to RS232, from 1 through 256 bytes.// ********************************************************************************************************CMD_READ_BYTES : beginbyte_count[16:8] <= 9'h0;byte_count[7:0] <= CMD_TRANSFER_SIZE ;host_addr_reg[23:0] <= CMD_ADDRESS_POINTER ;Function <= FUNC_READ ;tx_cyc<= 4'h0;RX_buffer[15*8+7:0] <= 128'h0;timeout_cnt <= DEBUG_WDT_TIME ; // set the receive abort timeout counterend // end of case CMD_READ_BYTES// ********************************************************************************************************// *** CMD_PREFIX case CMD_WRITE_BYTES. Read RS232 data and write into host ram, from 1 through 256 bytes.// ********************************************************************************************************CMD_WRITE_BYTES : beginbyte_count[16:8] <= 9'h0;byte_count[7:0] <= CMD_TRANSFER_SIZE ;host_addr_reg[23:0] <= CMD_ADDRESS_POINTER ;Function <= FUNC_WRITE ;RX_buffer[15*8+7:0] <= 128'h0;timeout_cnt <= DEBUG_WDT_TIME ; // set the receive abort timeout counterend // end of case CMD_WRITE_BYTES// ********************************************************************************************************// *** CMD_PREFIX case CMD_READ_BURST. Page read host ram and transmit to RS232, from 256 through 65536 bytes.// ********************************************************************************************************CMD_READ_BURST : beginbyte_count <= 1'b0;byte_count[15:8] <= CMD_TRANSFER_SIZE ;byte_count[7:0] <= 8'hFF ;host_addr_reg[23:0] <= CMD_ADDRESS_POINTER ;Function <= FUNC_READ ;tx_cyc<= 4'h0;RX_buffer[15*8+7:0] <= 128'h0;timeout_cnt <= DEBUG_WDT_TIME ; // set the receive abort timeout counterend // end of case CMD_READ_BURST// ********************************************************************************************************// *** CMD_PREFIX case CMD_WRITE_BURST. Page Read RS232 data and write into host ram, from 256 through 65536 bytes.// ********************************************************************************************************CMD_WRITE_BURST : beginbyte_count <= 1'b0;byte_count[15:8] <= CMD_TRANSFER_SIZE ;byte_count[7:0] <= 8'hFF ;host_addr_reg[23:0] <= CMD_ADDRESS_POINTER ;Function <= FUNC_WRITE ;RX_buffer[15*8+7:0] <= 128'h0;timeout_cnt <= DEBUG_WDT_TIME ; // set the receive abort timeout counterend // end of case CMD_WRITE_BURST// ********************************************************************************************************// *** CMD_PREFIX case CMD_RESET. Trigger the com_rst output.// ********************************************************************************************************CMD_RESET : beginrst_clk <= 4'h0;// Clearing the rst_clk which will begin the 8 clock reset periodFunction <= FUNC_WAIT ;RX_buffer[15*8+7:0] <= 128'h0;end // end of case CMD_RESET// *********************************************************************************************************************************************// *** CMD_PREFIX case CMD_SET_PORTS. Set the general purpose output ports and read general purpose input ports + the transmit 'ADDR_SIZE' parameter.// *********************************************************************************************************************************************CMD_SET_PORTS : beginout0<= RX_buffer[3*8+7:3*8] ; // The 4 out# ports were sent withing the RX_buffer command's suffix.out1<= RX_buffer[2*8+7:2*8] ; // Usually, the address and data transfer size is stored here.out2<= RX_buffer[1*8+7:1*8] ;out3<= RX_buffer[0*8+7:0*8] ;// *** Parallel register all 4 peripheral input ports.in_reg0<= in0 ;in_reg1<= in1 ;in_reg2<= in2 ;in_reg3<= in3 ;byte_count <= 1'b0 ;byte_count[15:0] <= 16'h4 ; // There are 5 bytes to be sent, the 4 in_reg#[7:0] registers and the 'ADDR_SIZE' parameter.Function <= FUNC_SET_PORTS ;tx_cyc<= 4'h0 ;RX_buffer[15*8+7:0] <= 128'h0 ;timeout_cnt <= DEBUG_WDT_TIME ; // set the receive abort timeout counterend // end of case CMD_SET_PORTSendcase// ********************************************************************************************************// *** End of case (CMD_PREFIX) for the 6 possible commands:// *** CMD_READ_BYTES, CMD_READ_BURST, CMD_WRITE_BYTES, CMD_WRITE_BURST, CMD_RESET, CMD_SET_PORTS// ********************************************************************************************************end // End of Command verification if ( CMD_HEADER && CMD_VERIFY )end// ************************************************************************************************************************************************************************************// *** Ending case Function FUNC_WAIT.// ************************************************************************************************************************************************************************************// ************************************************************************************************************************************************************************************// *** Beginning Function FUNC_READ. This function will read 'byte_count+1' bytes of host ram and transmit the contents to the RS232 port's TXD.// ************************************************************************************************************************************************************************************FUNC_READ : beginif (~byte_count && timeout_cnt!=5'h0 ) begin // keep on transmitting until byte counter elapses by counting below 0.if ( uart_tx ) timeout_cnt <= DEBUG_WDT_TIME ; // If a character is transmitted, reset the watch dog timeout counterelse if ( tick_1KHz ) timeout_cnt <= timeout_cnt - 1'b1 ; // Otherwise, countdown the watch dog timer once at every 1KHz tick.if ( host_rd_rdy ) host_rdata_reg <= host_rdata ; // latch the host_rdata input if the host_read_rdy is set.if (~(uart_tx_full && tx_cyc==4'h0) && ~(~host_rd_rdy && tx_cyc==4'h1) ) begin // only run the tx_cyc counter when the UART RS232 transmitter// is ready to transmit the next character and the host_rd_ready// has gone high after tx_cyc 0 when the host_re_req pulse has been sent.tx_cyc <= tx_cyc + 1'b1; // increment this cycle counterif (tx_cyc==4'd0) host_rd_req <= 1'b1;// pulse the host_rd_req with the current valid addresselse host_rd_req <= 1'b0;if (tx_cyc==4'd13) uart_tbyte <= host_rdata_reg ;// expect the returned data ready within 12 clock cycles, and latch that data into the RS232 transmitter data input registerif (tx_cyc==4'd14) uart_tx <= 1'b1;// Trigger the RS232 transmit data enableelse uart_tx <= 1'b0;if (tx_cyc==4'd15) host_addr_reg <= host_addr_reg + 1'b1; // increment the host memory addressif (tx_cyc==4'd15) byte_count <= byte_count - 1'b1; // decrement the byte transfer size counterendend else begin // byte_count cycles has completed or timeout WDT has elapsed,Function <= FUNC_WAIT; // leave FUNC_READ and switch back to FUNC_WAIT for the next command.uart_tx <= 1'b0;host_rd_req <= 1'b0;endend // End of case FUNC_READ// ************************************************************************************************************************************************************************************// *** Ending of Function FUNC_READ.// ************************************************************************************************************************************************************************************// ************************************************************************************************************************************************************************************// *** Beginning Function FUNC_WRITE. This function will read 'byte_count+1' bytes from the RS232 port's RXD and send the data to the host ram + send a copy back to the RS232 port's TXD.// ************************************************************************************************************************************************************************************FUNC_WRITE : beginif (~byte_count && timeout_cnt!=5'h0 ) begin // keep on reading from RS232 until byte counter elapses, or the timeout counter has reached it's endif ( ena_rxd_dly ) timeout_cnt <= DEBUG_WDT_TIME ; // If a character is received, reset the watch dog timeout counterelse if ( tick_1KHz ) timeout_cnt <= timeout_cnt - 1'b1 ; // Otherwise, countdown the watch dog timer once at every 1KHz tick.if (ena_rxd_dly) host_wdata <= uart_rbyte ; // copy RS232 received data byte to the host memory data output portif (ena_rxd_dly) uart_tbyte <= uart_rbyte ; // copy RS232 received data byte to the RS232 transmitter output data portif (ena_rxd_dly) host_wr_ena <= 1'b1; // Trigger the host memory write enableelse host_wr_ena <= 1'b0;if (ena_rxd_dly) uart_tx <= 1'b1;// echo back received character by triggering the RS232 transmit data enableelse uart_tx <= 1'b0;if (ena_rxd_dly) host_addr_reg <= host_addr_reg + 1'b1 ; // increment the host memory addressif (ena_rxd_dly) byte_count <= byte_count - 1'b1 ; // decrement the byte transfer size counterend else begin // byte_count cycles has completed or timeout WDT has elapsed,Function <= FUNC_WAIT ; // leave FUNC_WRITE and switch back to FUNC_WAIT for the next command.host_wr_ena <= 1'b0 ;byte_count <= 1'b1 ;endend // End of case FUNC_WRITE// ************************************************************************************************************************************************************************************// *** Ending of Function FUNC_WRITE.// ************************************************************************************************************************************************************************************// ************************************************************************************************************************************************************************************// *** Beginning of Function FUNC_SET_PORTS. This function will send the 'ADDR_SIZE' parameter + the 4 in#[7:0] ports' data through the RS232's port's TXD.// ************************************************************************************************************************************************************************************FUNC_SET_PORTS : beginif (~byte_count) begin // keep on transmitting until byte counter elapsesif (~(uart_tx_full && tx_cyc==4'h0)) begintx_cyc <= tx_cyc + 1'b1 ;if (tx_cyc==4'd13) begincase (byte_count[2:0]) // Set the RS232 transmitter's data port with the correct data during the correct byte number3'h4 : uart_tbyte<= in_reg0 ;3'h3 : uart_tbyte<= in_reg1 ;3'h2 : uart_tbyte<= in_reg2 ;3'h1 : uart_tbyte<= in_reg3 ;3'h0 : uart_tbyte<= ADDR_SIZE[7:0] ;endcase // case (byte_count[2:0])endif (tx_cyc==4'd14) uart_tx <= 1'b1 ;// Trigger the RS232 transmit data enableelse uart_tx <= 1'b0 ;if (tx_cyc==4'd15) byte_count <= byte_count - 1'b1 ; // decrement the byte transfer size counterendend else begin // byte_count cycles has completed, leave FUNC_SET_PORTS and switch back to FUNC_WAIT for the next command.Function <= FUNC_WAIT ;uart_tx <= 1'b0 ;host_rd_req <= 1'b0 ;endend // End of case FUNC_WRITE_PORTS// ************************************************************************************************************************************************************************************// *** Ending of Function FUNC_SET_PORTS.// ************************************************************************************************************************************************************************************endcase // Case (Function)// ******************************************************************************************************************// *** End of case statement for the 4 possible functions, FUNC_WAIT, FUNC_READ, FUNC_WRITE, FUNC_SET_PORTS// ******************************************************************************************************************end // ~rstend // ~soft_rstend // always @posedgeendmodule

author: BrianHG
author_rank: Super Contributor
timestamp: November 26, 2019, 01:56:02 am
content: For Quartus users, here are the project files with the simulation test benches setup using Quartus' .vwf (Vector Waveform) files which contain the test stimulus source waveforms.Though QuartusIIv9.1 was used, I test opened these projects in Quartus Prime and upon opening them, all you need to do is choose a newer FPGA to switch the project to. The .vwf files will simulate after an initial compile as these projects have all their .rpt and compilation folders erased so they may fit here on EEVblog's forum.

author: BrianHG
author_rank: Super Contributor
timestamp: November 26, 2019, 02:05:40 am
content: For users who have an FTDI USB-RS232 converter cable or FT232 - TTL RS232 converter and wish to get over 50 frames per second refresh when scrolling and viewing memory through the RS232_Debugger, these settings in Window's 'Device Manager' vastly accelerate FTDI USB driver's response.See photo:

author: lawrence11
author_rank: Frequent Contributor
timestamp: November 26, 2019, 04:35:40 am
content: Interresting interresting.Can you make something with a tb, and many modules.And will you choose many test bench or a single huge test bench?How will you set yourself up to give flexibility to your starting conditions.All of that good stuff, maybe some clock crossing from external sources.Something to force to use the "double buffering technique".

author: BrianHG
author_rank: Super Contributor
timestamp: November 26, 2019, 10:48:31 am
content: Quote from: lawrence11 on November 26, 2019, 04:35:40 amYes, as you can see, in both simulations, I actually using part of my RS232 UART, it's transmitter on it's own as a 'MASTER' while the entire UART module is being used again as a slave or as part of the 'debugger' module.QuoteThe 2 operating together makes a larger test bench than just analyzing a single Verilog core. In a way, they are also self generating part of their intermediary signals. I personally make small test benches to test each core, and larger one, sometimes entire designs especially when I do graphics where I need to make sure an individual pixel arrives exactly on a specific clock, and to get that far, I need a bunch of Verilog core modules working together. Under these circumstances, this must be done since a line or row of missing pixels at the edge of a 1080p picture being generated on a monitor may easily be missed. These setups and the coding behind them can take months if you are doing everything from scratch and the first time around.QuoteIf you mean in my test-bench, I literally drew the starting waveforms in Quartus' vector waveform editor. That utility is like a GUI paint software for signals. However, it is time consuming and newer versions of Quartus Prime removed some nice features like 'cut' & 'paste' sections of the waveform.QuoteClock domain switching is a complicated issue. However, if your main FPGA clock operates at least twice as fast as the data you are capturing, like in my example UART which works out the initial falling edge of the RXD, (see in code how the 'rx_trigger' is created). The same trick may be used to trap data from any other asynchronous Verilog module, generating the flag to tell your high speed system clock logic that the data is ready to take a single snapshot of the results. If you slower clock domain is even slower than 1/2 clock, delaying that 'rx_trigger' example by 1 additional system clock will guarantee a proper read no matter the internal FPGA delays unless you are running outside your compiler's timing analyzer's specified FMAX.QuoteYou should only require double buffering if your 2 clock domains have slightly different frequencies, like somewhere in-between half and double. The only other reason is if you are filling a buffer of data in small burst to be fed out and FPGA's do have 2 clock domain FIFO functions for these circumstances.

author: BrianHG
author_rank: Super Contributor
timestamp: December 30, 2019, 07:07:45 am
content: I've just updated the RS232_Debugger_HEX-editor with the ability to save 16bit wide Quartus .mif files.Use the regular 'm' to save a regular 8 bit .mif file.Use a shift 'M' to save a 16 bit .mif file, in little-edian. Note that converting an 8 bit to 16 bit in Quartus' .mif editor only does big-edian conversion. So, this is why I had to add a save 16bit .mif in my hex editor.Though it is a mess, I've also included the basic source file. If I ever get the time, I'll re-do the hex editor with 'sane' programming.

author: BrianHG
author_rank: Super Contributor
timestamp: July 16, 2020, 01:58:24 am
content: New addition to the 'rs232_DEBUGGER.v', version 1.1. A new parameter 'READ_REQ_1CLK' which will make the 'host_rd_req' output pulse for only a single clock instead of staying high until the 'host_rd_rdy' has been received.See simulation:'rs232_DEBUGGER.v.txt' ver. 1.1 attached below.There are no changes to the 'SYNC_RS232_UART.v' located at the top of this thread.

author: BrianHG
author_rank: Super Contributor
timestamp: August 13, 2020, 10:06:14 pm
content: Bug fix, RS232_Debugger_Hex-Editor_V1.2.zip, August 13, 2020.A minor bug in the ASCII display window where any data which is changed to a 0xFF due to memory operation or pressing the page-up / page-down key might retain old cached character has been fixed.This was only an on-screen ASCII display issue, the data and the HEX view always showed the correct data.

author: BrianHG
author_rank: Super Contributor
timestamp: April 13, 2021, 01:59:08 am
content: Just created version 1.2.This new patch just moves the parameters to the top of the module description and this code has been tested compatible with ModelSim.

author: BrianHG
author_rank: Super Contributor
timestamp: September 05, 2021, 04:34:57 am
content: GitHub repository added.Now includes the .bas source code for the PC HexEditor host, IE, it can now be compiled for Linux with Linux Free Basic.https://github.com/BrianHGinc/Verilog-RS232-Synch-UART-RS232-Debugger-and-PC-host-RS232-Hex-editor

author: BrianHG
author_rank: Super Contributor
timestamp: January 14, 2022, 06:29:13 pm
content: Version 1.3, Jan 14, 2022.Now has an 'ENDIAN' setting which now shows you the correct 16bit values for 16bit ints.When editing cells, this also allows the use of the +/- keys to increment/decrement 16bit values with the new ENDIAN settings being taken into account.Also has a new Quartus save 32bit .mif files.Also, changing the ENDIAN before saving a 16bit or 32bit Quartus .mif file will also swap they byte order making it easy to render wide .mif files with reverse ENIANess.