wiki:OFDMReferenceDesign/Applications/Characterization

OFDM Reference Design - MAC/PHY Performance Characterization

This application characterizes the performance of the OFDM Reference Design and a user-supplied MAC implementation. This application utilizes multiple WARP nodes along with external PCs to orchestrate the experiments. The main focus of this design shows in real time how a computer running a client program written in a non-C language (TCL) can connect to a server, which interprets commands into warpnode management structures, using the server to send commands to the warp nodes, which execute the commands.

In this application, the WARP nodes have no MAC or IP addresses. Both nodes run the same program.

The following diagrams represent two possible topological configurations for the setup of this application:

Design Setup 1 (Recommended)

Design Setup 2

Requirements

Setup

  1. Connect each WARP node directly to the Ethernet interface of the router
  2. Configure the PC Ethernet interfaces with IP address on the same subnet (10.0.0.1 and 10.0.0.2, for example)
  3. Connect each PC directly to the Ethernet interface of the router

OFDM Reference Design Code

Summary: The following extensions to the OFDM Reference Design expand node control by allowing a server to relay requests to warpnodes. The requests are sent as different C struct types. Command and Traffic structs set board parameters, while Command structs are used to stop and start transmissions, as well as request data (Stat structs) about the transmission. This model places the boards in a state where data for wireless transmission is locally created instead of taken from the Ethernet. The two complete codes for the modifications OFDM are the attached files csmaMac.c and warpnet_node2.c

The development of creating an interactive warpnet control requires the introduction of new structure types for the warp platform. These two types are warpnodeStats (which contains Statistical Data) and warpnodeTraffic (which contains parameters for updating the board) The two types are defined as:

Struct containing node statistics:

typedef struct {
	unsigned char structType;
	unsigned char nodeID;
	unsigned char partnerID;
	///Reserved Byte
	unsigned char reserved0;
	///Used to track number of goodPackets
	unsigned int goodPackets;
	///Used to track number of partnerBadPackets
	unsigned int partnerBadPackets;
	///Used to track number of otherBadPackets
	unsigned int otherBadPackets;
	unsigned int rxBytes;
	unsigned int txBytes;
	///Time in clock cycles. Recall PPC405 clock freq is 240000000 HZ.
	unsigned long long time; 
} warpnodeStats;

Struct containing traffic parameters for a node:

typedef struct {
	unsigned char structType;
	///
	unsigned char nodeID;
	///Value of 1 denotes Transmit, Value of 0 denotes Receive
	unsigned char trafficMode; 
	///Reserved Byte
	unsigned char reserved0;
	///Interval between start of each packet (in usec)
	unsigned int txInterval; 
	///The length of the packet (in bytes)
	unsigned short txPacketLen;
	///Reserved Short
	unsigned short reserved1;
} warpnodeTraffic;

warpnet_node.h needs to be expanded with these two new structs.

Additionally, new pound defines are required for the new setup: Under Struct Types for WARP Node <-> Server Packets

#define STRUCTID_NODETRAFFIC 0x45

Under Node command codes

#define NODECMD_SENDPACKET 0x64
#define NODECMD_SENDSTATS 0x65
#define NODECMD_START 0x66
#define NODECMD_STOP 0x67
#define NODECMC_REQUESTSTATS 0x68

The traffic struct include a parameter called trafficMode, which specifies whether a board is transmitting or receiving. Include two #defines for Transmit and Receive:

#define TRANSMIT 1
#define RECEIVE 0

Help: In warpmac.c and csmaMac.c you could comment out warpnet_node.h and replace it with the attached warpnet_node2.h

The remainder of code extensions will occur in csmaMac.c

For the new extension to the previous frame work, the following global variables will be required:

///Global Stat struct
warpnodeStats myStats[WARPNET_NUMNODES];

///Place holder for TRANSMIT or RECEIVE state
unsigned char Mode;

///Measures time for beginning and end of transmission
XTime start_timer, end_timer;

unsigned int pktCount_good, pktCount_bad;

///Parameters used for warpmac_startPacketGeneration
unsigned int Time;
unsigned short packetlength;

This implementation of the OFDM Reference Design uses two similar functions for transmitting ack packets and stat packets when requested. Additionally, both functions require the seqNum as a parameter in order to ensure proper packet organization on the server-client side.

The design of sendAck sends a warpnodeCommand with values, which denote it is the ack version of the warpnodeCommand (myNodeCmd.cmdID = NODECMD_NODEACK).

The full design for sendAck is as follows:

void sendAck(char rxSeqNum)
{
	//Initialize Payload Address
	int pktBuf_payloadAddr;

	//Fill in a node command reply pkt
	myNodeCmd.structType = STRUCTID_NODECOMMAND;
	myNodeCmd.nodeID = myID;
	myNodeCmd.cmdID = NODECMD_NODEACK;
	myNodeCmd.cmdParam = rxSeqNum;
							
	//Fill in the ethernet packet's header info
	txEthPktHeader.ethType = WARPNET_ETHTYPE_NODE2SVR;
	memset(&(txEthPktHeader.srcAddr), (unsigned char)myID, 6); //Generic source address; ignored throughout
	memset(&(txEthPktHeader.dstAddr), (unsigned char)0xFF, 6); //Broadcast destination address
						
	//Fill in the ethernet packet's header info
	txEthPktHeader.pktLength = sizeof(warpnetEthernetPktHeader) + sizeof(warpnodeCommand);
	txEthPktHeader.numStructs = 1;
	txEthPktHeader.seqNum = rxSeqNum;
	
	//Copy over data for transmission over wire
	pktBuf_payloadAddr = warpphy_getBuffAddr(WARPNET_PKTBUFFINDEX);
	memcpy((void *)pktBuf_payloadAddr, &(txEthPktHeader), sizeof(warpnetEthernetPktHeader));
	memcpy((void *)pktBuf_payloadAddr+sizeof(warpnetEthernetPktHeader), &(myNodeCmd), sizeof(warpnodeCommand));
	
	//Send the packet over the wire	
	warpmac_sendRawEthernetPacket((void *)pktBuf_payloadAddr, txEthPktHeader.pktLength);
	return;
}

The design of sendStatPacket requires a few small modifications:

void sendStatsPacket(char rxSeqNum)
{
	//Initialize Payload Address
	unsigned int pktBuf_payloadAddr;
	
	//Fill in the ethernet packet's header info
	txEthPktHeader.pktLength = sizeof(warpnetEthernetPktHeader) + (WARPNET_NUMNODES*sizeof(warpnodeStats));
	txEthPktHeader.numStructs = WARPNET_NUMNODES;
	txEthPktHeader.seqNum = rxSeqNum;

	//Copy the ethernet header and stats payload to a memroy buffer
	pktBuf_payloadAddr = warpphy_getBuffAddr(WARPNET_PKTBUFFINDEX);
	memcpy((void *)pktBuf_payloadAddr, &(txEthPktHeader), sizeof(warpnetEthernetPktHeader));
	
	//Copy each stats struct (one per partner)
	memcpy((void *)pktBuf_payloadAddr+sizeof(warpnetEthernetPktHeader), &myStats, WARPNET_NUMNODES*sizeof(warpnodeStats));
	
	//Send the packet over the wire
	warpmac_sendRawEthernetPacket((void *)pktBuf_payloadAddr, txEthPktHeader.pktLength);
	
	return;
}

The function processControlStruct updates the node with the parameters in the warpnodeControl struct:

void processControlStruct(warpnodeControl* ctrlStruct)
{
	//xil_printf("Processing Control Struct\r\n");
	//Interpret the control struct's modulation rate value
	switch(ctrlStruct->modOrder)
	{
			/*** Non-autorate version ***/
		case 0x1: //BPSK
			pktFullRate = HDR_FULLRATE_BPSK;
			warp_userio_lcd_printline("Mod order: BPSK ", 16, 8, 1);
			break;
		case 0x2: //QPSK
			pktFullRate = HDR_FULLRATE_QPSK;
			warp_userio_lcd_printline("Mod order: QPSK ", 16, 8, 1);
			break;
		case 0x4: //16-QAM
			pktFullRate = HDR_FULLRATE_QAM_16;
			warp_userio_lcd_printline("Mod order: 16QAM", 16, 8, 1);
			break;
		case 0x6: //64-QAM
			pktFullRate = HDR_FULLRATE_QAM_64;
			warp_userio_lcd_printline("Mod order: 64QAM", 16, 8, 1);
			break;
			/*******************/
		default:
			//xil_printf("Invalid Mod order - Recieved: %x\r\n", ctrlStruct->modOrder);
			//Invalid value, so ignore it and leave txFulrate as-is
			break;
	}
	
	//Set the transmit power for both radios
	warpphy_setTxPower(ctrlStruct->txPower);
	//xil_printf("Power changed to %d\r\n", ctrlStruct->txPower);
	
	//Use the circle of LEDs as a Tx power indicator
	warp_userioboard_set_leds((1<<((8+(ctrlStruct->txPower))/8))-1);
	unsigned char charsToPrint[16];
	snprintf(charsToPrint, 16, "Tx Pwr: 0x%2x    ", (ctrlStruct->txPower));
	
	//xil_printf("Tx Power changed to: %x\r\n", ctrlStruct->txPower);
	
	warp_userio_lcd_printline(charsToPrint, 16, 6, 1);
	
	//Set the center frequency of both radios
	// Values 1-14 are 2.4GHz channels
	// Values 15-37 are 5GHz channels
	// Any other value is invalid and ignored
	if( ((ctrlStruct->channel) > 0) && ((ctrlStruct->channel) <= 37) )
	{
		if((ctrlStruct->channel) <= 14)
			warpphy_setChannel(GHZ_2, ctrlStruct->channel);
		else
			warpphy_setChannel(GHZ_5, (ctrlStruct->channel) - 14);
	}
	
	snprintf(charsToPrint, 16, "Channel: %2d     ", (ctrlStruct->channel));
	warp_userio_lcd_printline(charsToPrint, 16, 7, 1);
	
	return;
}

In previous designs of warpnet, the emacRx_callback was called when data was specified for the node and when it was specified for transmission over the air. The new design makes a distinction between these two types through mgmtFromNetworkLayer_callback and dataFromNetworkLayer_callback

The mgmtFromNetworkLayer_callback extends previous code used to process management structures because this callback processes the new traffic struct and the new node commands.

Because the nodes are on the same Ethernet network, the management callback checks if the Ethernet type is from the server to node or vice-versa. If a packet is a NODE2SVR, the node has received an ack packet or stat packet from the other node. Because both nodes are on the same network and see all traffic on the network, the implementation for SVR2NODE nodes checks if the nodeID in the structs equals myID.

In the NODECMD, check the cmdStruct->cmdID for the IDs of NODECMD_RESETSTATS, NODECMD_START, NODECMD_STOP, and NODECMC_REQUESTSTATS:

NODECMD_RESETSTATS needs to reset all values in myStats and send an ack packet if requested:

case NODECMD_RESETSTATS:				
	if( (cmdStruct->nodeID) == myID)
	{	
		//Reset the good/bad pkt counts and Rx/Tx byte counts
		for(i=0; i<WARPNET_NUMNODES; i++)
		{
			myStats[i].goodPackets = 0;
			myStats[i].partnerBadPackets = 0;
			myStats[i].otherBadPackets = 0;
			myStats[i].txBytes = 0;
			myStats[i].rxBytes = 0;
			myStats[i].time = 0;
		}	
		//Send Ack Packet
	if(rxSeqNum > 0) sendAck(rxSeqNum);
	}

NODECMD_START sets a xtime variable start_timer and begins packet generation if the traffic struct had set Mode to TRANSMIT:

case NODECMD_START:					
	XTime_GetTime(&start_timer);
	if (Mode==TRANSMIT){
	warpmac_startPacketGeneration(packetlength,Time); //second argument is interval in usec
	}

NODECMD_STOP stops packet generation, calculates the time difference, updates the time variable in myStats, and sends an ack if requested:

case NODECMD_STOP:
	//xil_printf("NODECMC_STOP \r\n");
	warpmac_stopPacketGeneration();
	
	XTime_GetTime(&end_timer);
	long long xtime=0;
	xtime=end_timer-start_timer;
	for(i=0; i<WARPNET_NUMNODES; i++)
	{
		myStats[i].time = xtime;
	}
	
	if((cmdStruct->nodeID) == myID)
	{	
	//Send Ack Packet
	if(rxSeqNum > 0) sendAck(rxSeqNum);
	}

NODECMC_REQUESTSTATS sends the stats packet and ack packet if requested:

case NODECMC_REQUESTSTATS:
	if( (cmdStruct->nodeID) == myID)
	{	
		sendStatsPacket(rxSeqNum);
							
		//Send Ack Packet
		if(rxSeqNum > 0) sendAck(rxSeqNum);
	}

Whenever phyRx_badHeader_callback is called, the otherBadPackets count should be incremented:

	int i;
	//Update every stats struct, since we don't know where this bad pkt came from
	for(i=0; i<WARPNET_NUMNODES; i++)
	{
		myStats[i].otherBadPackets++;
	}

The new design implements a STRUCTID_NODETRAFFIC, which updates the interval between packets, the transmission mode, and packet length:

case STRUCTID_NODETRAFFIC: //This is for a traffic struct
	trafficStruct = (warpnodeTraffic*)(payload+rxPktOffset);
	
	if((trafficStruct->nodeID) == myID)
	{
	//Send Ack Packet
	if(rxSeqNum > 0) sendAck(rxSeqNum);
	
	Time=trafficStruct->txInterval;
	Mode=trafficStruct->trafficMode;
	packetlength=trafficStruct->txPacketLen;
	}		
rxPktOffset += sizeof(warpnodeTraffic);

When phyRx_goodHeader_callback is called, either the count for goodPackets or partnerBadPackets is incremented.

If the callback enters the if(state&GOOD) update the goodPacket count, rxBytes and txBytes as follows:

myStats[srcNode].goodPackets++;
myStats[srcNode].rxBytes += (packet->header.length + NUM_HEADER_BYTES + NUM_PAYLOAD_CRC_BYTES + NUM_PAYLOAD_TAIL_BYTES);
myStats[srcNode].txBytes += NUM_HEADER_BYTES; 

If the callback enters the if(state&BAD) update the partnerBadPackets:

	myStats[srcNode].partnerBadPackets++;

In the main function, set warpmac_enableDataFromNetwork, warpmac_setBaseRate, and warpmac_enableDummyPacketMode. For this application, the boards will be left in DummyPacketMode to prevent the nodes from transmitting information from the wire. (Due to the nature of the set up with routers, the TCP ack packets sent from the TCL-client computer to the server computer will be sent over the wireless channel. This can create problem because the computers will retransmit the data when they don't receive TCP-acks)

Set these global variables to default values:

	Mode=0;
	sendStats=0;
	Time=0;
	packetlength=1470;
	
	int i;	
	//Initialize the Stats Structs
	for(i=0; i<WARPNET_NUMNODES; i++)
	{
		txSequences[i] = 1;
		rxSequences[i] = 0;
		myStats[i].structType = STRUCTID_NODESTATS;
		myStats[i].nodeID = myID;
		myStats[i].partnerID = i;
		myStats[i].goodPackets = 0;
		myStats[i].otherBadPackets = 0;
		myStats[i].partnerBadPackets = 0;
		myStats[i].rxBytes = 0;
		myStats[i].txBytes = 0;
		myStats[i].reserved0 = 0;
	}

Include the callback for management packets:

	warpmac_setMgmtFromNetworkCallback((void *)mgmtFromNetworkLayer_callback);

Because csmaMac uses randomization to offset packet transmission, the random number generators need to be seeded to different values. Although multiple methods can be used to seed the random number generator, one method uses the xor of the bytes of the node ID with a random clock cycle input and WarpRadio_v1_RSSIData:

	(myID ^ ((int) rand_time) ^ WarpRadio_v1_RSSIData(RADIO2_ADDR));

Lastly, the Ethernet Packet Header is initialized with the Source Address and Destination Address:

	txEthPktHeader.ethType = WARPNET_ETHTYPE_NODE2SVR;
	memset(&(txEthPktHeader.srcAddr), (unsigned char)myID, 6); //Generic source address; ignored throughout
	memset(&(txEthPktHeader.dstAddr), (unsigned char)0xFF, 6); //Broadcast destination address

Dumb Server Code

Summary: The attached file dumb_server.zip contains the code for receiving data from the Tcl Client, processing this data, and sending the appropriate structs to the warp nodes. The current setup has the code connect to the Tcl Client over port 9090 and has the PC with the server_code on 10.0.0.6

Because this page is made to show how one can use tcl to control warp nodes, we will assume the dumb server can change tcl strings to warpnode management structs without going into the details.

Note:If you connect more nodes to the dumb server for a larger setup, you may need to set the numWARPnodes to a number besides2 in the main function

Tcl Client Code

Summary: The Tcl Client automates control of warpnode parameters. The Tcl Client sends values to the server. Attached are two versions of the tcl client. The automated_client.tcl, which loops through changing txpowers and modOrders with a set time interval, and the interactive_client.tcl, which pauses before transmitting a request to start transmission on nodes, asks for a time interval or defaults to a user input for stopping the data transmission. The Tcl Client can either be on the same machine running the Dumb Server code or be on a machine connected to the router. To run a tcl program, open up a command line such as a terminal and type:

 tclsh filename.tcl

The following will explain how to create an automated test similar to automate_client.tcl Initially, a few user specified global parameters need to be set. This includes the filename for where statistics are written, the number of nodes, and information for connecting to the socket. In this set up, the dumb_server contains the IP address of 10.0.0.6 and connects through port 9090.

set filename "PacketData.m"
set NumNodes 2;
set dumbServerIP 10.0.0.6
set dumbServerPort 9090

To main code for developing an automated experiment that writes statistic data to a file begins by connecting the socket with the dumb server and configuring the socket to automate flushing of the memory

#Open a socket connection
set sock [socket $dumbServerIP $dumbServerPort] 

#Automate flushing of the socket
fconfigure $sock -buffering none -blocking 0

Next a default time interval is set and an blank file $filename is created (clears the file if it contains data)

set TimeInterval 100
;#Create Blank File
set fileID [open $filename "w"] 

A create a test for sweeping through txpowers would use a for loop that modifies the txpower value in the control structure.

The following lists supported structures definitions and examples of how to use them:

;# Control Struct: structType, nodeID, modOrder, txPower, coding, channel
set ControlStruct0 [list 50 0 4 63 3 9 ]
set ControlStruct1 [list 50 1 4 63 3 9 ]

;# Traffic Struct: structType, nodeID, trafficMode 0 = RECEIVE 1 = TRANSMIT , reserved0, txInterval, txPacketLen, reserved1 
set TrafficStruct0 [list 52 0 1 0 0 1470 0 ]
set TrafficStruct1 [list 52 1 1 0 0 1470 0 ]

;# structType, nodeID is only relevant for ack on stop, cmdID, cmdParam
set CommandStructStart [list 51 0 102 0 ]
set CommandStructStop [list 51 0 103 0 ]

;# structType, nodeID, cmdID, cmdParam
set CommandStructStats0 [list 51 0 104 0 ]
set CommandStructStats1 [list 51 1 104 0 ]
set CommandStructResetStats0 [list 51 0 97 0 ]
set CommandStructResetStats1 [list 51 1 97 0 ]

The function senddata sends a struct to the dumb server. The first parameter is the struct to send. The second parameter specifies modes, such as requesting an ackPacket with $SendAck or statPacket from the server with $WriteStat. The default parameter requests nothing from the server.

The following highlights a test for sweeping through txPowers from 0 to 63 while changing from QPSK to 16QAM:

for {set power 0} {$power < 64} {incr power} {
	for {set mod 1} {$mod<3} {incr mod} { ;#set mod
	
#Send a Control Struct to node 0
senddata $sock [list 50 0 [expra  {$mod*2}] $power 3 9 ] $SendAck
#Send a Control Struct to node 1
senddata $sock [list 50 1 [expr {$mod*2}] $power 3 9 ] $SendAck

#Send a Traffic Struct to node 0
senddata $sock [list 52 0 1 0 0 1470 0 ] $SendAck
#Send a Traffic Struct to node 1
senddata $sock [list 52 1 1 0 0 1470 0 ] $SendAck

#Reset Statistics on nodes, sets everything to 0
senddata $sock $CommandStructResetStats0 $SendAck
senddata $sock $CommandStructResetStats1 $SendAck

#Begin data transmission over the air
senddata $sock $CommandStructStart
after $TimeInterval
#Stop data tranmission over the air
senddata $sock $CommandStructStop $SendAck

senddata $sock $CommandStructStats0 $WriteStat
senddata $sock $CommandStructStats1 $WriteStat
	
	}
}

The last step is to close the socket and exit the tcl script:

close $sock
exit
Last modified 15 years ago Last modified on Jul 2, 2009, 9:52:00 AM

Attachments (7)

Download all attachments as: .zip