sim_in.pilot = pilot;
sim_in.tx_pilot_buf = [pilot; pilot; pilot];
if sim_in.do_write_pilot_file
- write_pilot_file(pilot, Nsymbrowpilot, Ns, Nsymbrow, Npilotsframe, Nc);
+ write_pilot_file(pilot, Nsymbrowpilot, Ns, Np, Nsymbrow, Npilotsframe, Nc);
end
% Init LDPC --------------------------------------------------------------------
% Symbol rate processing for tx side (modulator) -------------------------------------------------------
-function [tx_symb tx_bits prev_sym_tx] = symbol_rate_tx(sim_in, tx_bits, code_param, prev_sym_tx)
+function [tx_symb tx_bits prev_sym_tx] = bits_to_qpsk_symbols(sim_in, tx_bits, code_param, prev_sym_tx)
ldpc_code = sim_in.ldpc_code;
rate = sim_in.ldpc_code_rate;
framesize = sim_in.framesize;
endfunction
-function write_pilot_file(pilot, Nsymbrowpilot, Ns, Nsymrow, Npilotsframe, Nc);
+function write_pilot_file(pilot, Nsymbrowpilot, Ns, Np, Nsymrow, Npilotsframe, Nc);
filename = sprintf("../src/cohpsk_defs.h", Npilotsframe, Nc);
f=fopen(filename,"wt");
fprintf(f,"/* Generated by write_pilot_file() Octave function */\n\n");
fprintf(f,"#define NSYMROW %d /* number of data symbols for each row (i.e. each carrier) */\n", Nsymrow);
- fprintf(f,"#define NS %d /* number of data symbols between pilots */\n", Ns);
- fprintf(f,"#define NPILOTSFRAME %d /* number of pilot symbols of each row */\n", Npilotsframe);
- fprintf(f,"#define PILOTS_NC %d /* number of carriers in coh modem */\n\n", Nc);
- fprintf(f,"#define NSYMROWPILOT %d /* length of row after pilots inserted */\n\n", Nsymbrowpilot);
+ fprintf(f,"#define NS %d /* number of data symbols between pilots */\n", Ns);
+ fprintf(f,"#define NP %d /* number of pilots to use for channel est */\n", Np);
+ fprintf(f,"#define NPILOTSFRAME %d /* number of pilot symbols of each row */\n", Npilotsframe);
+ fprintf(f,"#define PILOTS_NC %d /* number of carriers in coh modem */\n\n", Nc);
+ fprintf(f,"#define NSYMROWPILOT %d /* length of row after pilots inserted */\n\n", Nsymbrowpilot);
fclose(f);
filename = sprintf("../src/pilots_coh.h", Npilotsframe, Nc);
tx_bits = round(rand(1,framesize));
end
- [s_ch tx_bits prev_sym_tx] = symbol_rate_tx(sim_in, tx_bits, code_param, prev_sym_tx);
+ [s_ch tx_bits prev_sym_tx] = bits_to_qpsk_symbols(sim_in, tx_bits, code_param, prev_sym_tx);
tx_bits_buf(1:framesize) = tx_bits_buf(framesize+1:2*framesize);
tx_bits_buf(framesize+1:2*framesize) = tx_bits;
load ../build_linux/unittest/tcohpsk_out.txt
sim_in = standard_init();
+sim_in.framesize = 160;
+sim_in.ldpc_code = 0;
+sim_in.ldpc_code_rate = 1;
+sim_in.Nc = 4;
+sim_in.Rs = 50;
+sim_in.Ns = 4;
+sim_in.Np = 2;
+sim_in.Nchip = 1;
+sim_in.modulation = 'qpsk';
+sim_in.do_write_pilot_file = 0;
+sim_in = symbol_rate_init(sim_in);
+
+rand('state',1);
tx_bits = round(rand(1,framesize));
tx_bits_log = [];
+tx_symb_log = [];
for i=1:frames
tx_bits_log = [tx_bits_log tx_bits];
+ [tx_symb tx_bits prev_tx_sym] = bits_to_qpsk_symbols(sim_in, tx_bits, [], []);
+ tx_symb_log = [tx_symb_log; tx_symb];
end
-stem_sig_and_error(1, 211, tx_bits_log_c(1:n), tx_bits_log(1:n) - tx_bits_log_c(1:n), 'tx bits', [1 n -1.5 1.5])
+stem_sig_and_error(1, 111, tx_bits_log_c(1:n), tx_bits_log(1:n) - tx_bits_log_c(1:n), 'tx bits', [1 n -1.5 1.5])
+stem_sig_and_error(2, 211, real(tx_symb_log_c(1:n)), real(tx_symb_log(1:n) - tx_symb_log_c(1:n)), 'tx symb re', [1 n -1.5 1.5])
+stem_sig_and_error(2, 212, imag(tx_symb_log_c(1:n)), imag(tx_symb_log(1:n) - tx_symb_log_c(1:n)), 'tx symb im', [1 n -1.5 1.5])
check(tx_bits_log, tx_bits_log_c, 'tx_bits');
+check(tx_symb_log, tx_symb_log_c, 'tx_symb');
#include "comp.h"
-void bits_to_qpsk_symbols(COMP tx_symb[][COHPSK_NC], int tx_bits[], int framesize);
+struct COHPSK;
+
+struct COHPSK *cohpsk_create(void);
+void cohpsk_destroy(struct COHPSK *coh);
+void bits_to_qpsk_symbols(COMP tx_symb[][COHPSK_NC], int tx_bits[], int nbits);
+void qpsk_symbols_to_bits(struct COHPSK *coh, int rx_bits[], COMP rx_symb[][COHPSK_NC]);
#endif
#include "codec2_cohpsk.h"
#include "test_bits.h"
#include "cohpsk_defs.h"
+#include "cohpsk_internal.h"
#include "pilots_coh.h"
+#include "comp_prim.h"
static COMP qpsk_mod[] = {
{ 1.0, 0.0},
\*---------------------------------------------------------------------------*/
+/*--------------------------------------------------------------------------* \
+
+ FUNCTION....: cohpsk_create
+ AUTHOR......: David Rowe
+ DATE CREATED: Marcg 2015
+
+ Create and initialise an instance of the modem. Returns a pointer
+ to the modem states or NULL on failure. One set of states is
+ sufficient for a full duplex modem.
+
+\*---------------------------------------------------------------------------*/
+
+struct COHPSK *cohpsk_create(void)
+{
+ struct COHPSK *coh;
+
+ coh = (struct COHPSK*)malloc(sizeof(struct COHPSK));
+ if (coh == NULL)
+ return NULL;
+
+ /* set up buffer of 3 frames of tx pilot symbols */
+
+ memcpy(&coh->tx_pilot_buf[0][0], pilots_coh, sizeof(pilots_coh));
+ memcpy(&coh->tx_pilot_buf[NPILOTSFRAME][0], pilots_coh, sizeof(pilots_coh));
+ memcpy(&coh->tx_pilot_buf[2*NPILOTSFRAME][0], pilots_coh, sizeof(pilots_coh));
+
+ return coh;
+}
+
+
+/*---------------------------------------------------------------------------*\
+
+ FUNCTION....: cohpsk_destroy
+ AUTHOR......: David Rowe
+ DATE CREATED: March 2015
+
+ Destroy an instance of the modem.
+
+\*---------------------------------------------------------------------------*/
+
+void cohpsk_destroy(struct COHPSK *coh)
+{
+ assert(coh != NULL);
+ free(coh);
+}
+
+
/*---------------------------------------------------------------------------*\
FUNCTION....: bits_to_qpsk_symbols()
AUTHOR......: David Rowe
DATE CREATED: March 2015
- Maps bits to parallel DQPSK symbols and inserts pilot symbols.
+ Rate Rs modulator. Maps bits to parallel DQPSK symbols and inserts pilot symbols.
\*---------------------------------------------------------------------------*/
-void bits_to_qpsk_symbols(COMP tx_symb[][PILOTS_NC], int tx_bits[], int framesize)
+void bits_to_qpsk_symbols(COMP tx_symb[][PILOTS_NC], int tx_bits[], int nbits)
{
COMP tx_symb_data[NSYMROW][PILOTS_NC];
int i, r, c, p_r, data_r;
short bits;
assert(COHPSK_NC == PILOTS_NC);
- assert((NSYMROW*PILOTS_NC)*2 == framesize);
+ assert((NSYMROW*PILOTS_NC)*2 == nbits);
/*
Organise QPSK symbols into a NSYMBROWS rows by PILOTS_NC cols matrix,
for(i=0; i<NS; data_r++,r++,i++) {
for(c=0; c<PILOTS_NC; c++) {
tx_symb[r][c] = tx_symb_data[data_r][c];
- //printf("r: %d c: %d data_r: %d %r %r\n", r, c, data_r, tx_symb[r][c]);
}
}
}
assert(data_r == NSYMROW);
assert(r == NSYMROWPILOT);
}
+
+
+/*---------------------------------------------------------------------------*\
+
+ FUNCTION....: bits_to_qpsk_symbols()
+ AUTHOR......: David Rowe
+ DATE CREATED: March 2015
+
+ Rate Rs demodulator. Extract pilot symbols and estimate amplitude and phase
+ of each carrier. Correct phase of data symbols and convert to bits.
+
+\*---------------------------------------------------------------------------*/
+
+void qpsk_symbols_to_bits(struct COHPSK *coh, int rx_bits[], COMP rx_symb[][COHPSK_NC])
+{
+ int st, en, r, c, i, p_r, data_r;
+ COMP ch_est, rot, pi_on_4;
+
+ pi_on_4.real = cosf(M_PI/4); pi_on_4.imag = sinf(M_PI/4);
+
+ /* extract pilot and data symbols into 3 frame buffers */
+
+ for (r=0, p_r=2*NPILOTSFRAME, data_r=2*NSYMROW; r<NSYMROWPILOT; p_r++) {
+
+ printf("r: %d data_r: %d\n", r, data_r);
+ /* copy row of pilots onto end of pilot buffer */
+
+ for(c=0; c<PILOTS_NC; c++) {
+ coh->rx_pilot_buf[p_r][c] = rx_symb[r][c];
+ //printf(" %d %d %f %f", p_r, c, coh->rx_pilot_buf[p_r][c].real, coh->rx_pilot_buf[p_r][c].imag);
+ }
+ //printf("\n");
+ r++;
+
+ //printf("r: %d data_r: %d\n", r, data_r);
+ /* copy NS rows of data symbols onto end of data symbol buffer */
+
+ for(i=0; i<NS; data_r++,r++,i++) {
+ for(c=0; c<PILOTS_NC; c++)
+ coh->rx_symb_buf[data_r][c] = rx_symb[r][c];
+ }
+ }
+
+ /* estimate channel amplitude and phase and correct data symbols in middle of buffer */
+
+ for (r=0, data_r=NSYMROW; r<NSYMROW; r++, data_r) {
+
+ /* pilots to use for correcting data_r-th symbol */
+
+ st = NPILOTSFRAME + floor(r/NS) - floor(NP/2) + 1;
+ en = st + NP - 1;
+ assert(st >= 0);
+ assert(en < 3*NPILOTSFRAME);
+
+ printf("r: %d data_r: %d st: %d en: %d\n", r, data_r, st, en);
+
+ /* iterate over all of the carriers */
+
+ for (c=0; c<PILOTS_NC; c++) {
+
+ /* estimate channel */
+
+ ch_est.real = 0.0; ch_est.imag = 0.0;
+ for (i=st; i<=en; i++)
+ ch_est = cadd(ch_est, fcmult(coh->tx_pilot_buf[i][c], coh->rx_pilot_buf[i][c]));
+ ch_est = fcmult(1.0/NP, ch_est);
+ coh->phi_[r][c] = atan2(ch_est.imag,ch_est.real);
+ coh->amp_[r][c] = cabsolute(ch_est);
+
+ /* correct phase */
+
+ rot.real = cosf(coh->phi_[r][c]); rot.imag = -sinf(coh->phi_[r][c]);
+ coh->rx_symb_buf[data_r][c] = cmult(coh->rx_symb_buf[data_r][c], rot);
+
+ /* demodulate */
+
+ i = c*NSYMROW + r;
+ rot = cmult(rx_symb[data_r][c], pi_on_4);
+ rx_bits[2*i] = rot.real < 0;
+ rx_bits[2*i+1] = rot.imag < 0;
+
+ printf(" c: %d ch_est: %f %f phi_: %f amp_: %f\n",c, ch_est.real, ch_est.imag, coh->phi_[r][c], coh->amp_[r][c]);
+ }
+ //exit(0);
+ }
+
+ /* shift buffers */
+
+ memcpy(&coh->rx_pilot_buf[0][0], &coh->rx_pilot_buf[NPILOTSFRAME][0], sizeof(COMP)*2*NPILOTSFRAME*PILOTS_NC);
+ memcpy(&coh->rx_symb_buf[0][0], &coh->rx_symb_buf[NSYMROW][0], sizeof(COMP)*2*NSYMROW*PILOTS_NC);
+
+}
/* Generated by write_pilot_file() Octave function */
#define NSYMROW 20 /* number of data symbols for each row (i.e. each carrier) */
-#define NS 4 /* number of data symbols between pilots */
-#define NPILOTSFRAME 5 /* number of pilot symbols of each row */
-#define PILOTS_NC 4 /* number of carriers in coh modem */
+#define NS 4 /* number of data symbols between pilots */
+#define NP 2 /* number of pilots to use for channel est */
+#define NPILOTSFRAME 5 /* number of pilot symbols of each row */
+#define PILOTS_NC 4 /* number of carriers in coh modem */
-#define NSYMROWPILOT 25 /* length of row after pilots inserted */
+#define NSYMROWPILOT 25 /* length of row after pilots inserted */
--- /dev/null
+/*---------------------------------------------------------------------------*\
+
+ FILE........: cohpsk_internal.h
+ AUTHOR......: David Rowe
+ DATE CREATED: March 2015
+
+ Functions that implement a coherent PSK FDM modem.
+
+\*---------------------------------------------------------------------------*/
+
+/*
+ Copyright (C) 2015 David Rowe
+
+ All rights reserved.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License version 2.1, as
+ published by the Free Software Foundation. This program is
+ distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
+ License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __COHPSK_INTERNAL__
+#define __COHPSK_INTERNAL__
+
+struct COHPSK {
+ float tx_pilot_buf[3*NPILOTSFRAME][PILOTS_NC]; /* 3 frames of tx pilot symbols */
+ COMP rx_pilot_buf[3*NPILOTSFRAME][PILOTS_NC]; /* 3 frames of rx piloy symbols */
+ COMP rx_symb_buf[3*NSYMROW][PILOTS_NC]; /* 3 frames of rx data symbols */
+ float phi_[NSYMROW][PILOTS_NC]; /* phase estimates for this frame of rx data symbols */
+ float amp_[NSYMROW][PILOTS_NC]; /* amplitude estimates for this frame of rx data symbols */
+};
+
+#endif
--- /dev/null
+/*---------------------------------------------------------------------------*\
+
+ FILE........: comp_prim.h
+ AUTHOR......: David Rowe
+ DATE CREATED: Marh 2015
+
+ Complex number maths primitives.
+
+\*---------------------------------------------------------------------------*/
+
+/*
+ Copyright (C) 2015 David Rowe
+
+ All rights reserved.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License version 2.1, as
+ published by the Free Software Foundation. This program is
+ distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public
+ License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with this program; if not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __COMP_PRIM__
+#define __COMP_PRIM__
+
+/*---------------------------------------------------------------------------*\
+
+ FUNCTIONS
+
+\*---------------------------------------------------------------------------*/
+
+static COMP cneg(COMP a)
+{
+ COMP res;
+
+ res.real = -a.real;
+ res.imag = -a.imag;
+
+ return res;
+}
+
+static COMP cconj(COMP a)
+{
+ COMP res;
+
+ res.real = a.real;
+ res.imag = -a.imag;
+
+ return res;
+}
+
+static COMP cmult(COMP a, COMP b)
+{
+ COMP res;
+
+ res.real = a.real*b.real - a.imag*b.imag;
+ res.imag = a.real*b.imag + a.imag*b.real;
+
+ return res;
+}
+
+static COMP fcmult(float a, COMP b)
+{
+ COMP res;
+
+ res.real = a*b.real;
+ res.imag = a*b.imag;
+
+ return res;
+}
+
+static COMP cadd(COMP a, COMP b)
+{
+ COMP res;
+
+ res.real = a.real + b.real;
+ res.imag = a.imag + b.imag;
+
+ return res;
+}
+
+static float cabsolute(COMP a)
+{
+ return sqrtf(powf(a.real, 2.0) + powf(a.imag, 2.0));
+}
+
+#endif
#include "fdmdv_internal.h"
#include "codec2_fdmdv.h"
+#include "comp_prim.h"
#include "rn.h"
#include "rxdec_coeff.h"
#include "test_bits.h"
#define printf gdb_stdio_printf
#endif
-/*---------------------------------------------------------------------------*\
-
- FUNCTIONS
-
-\*---------------------------------------------------------------------------*/
-
-static COMP cneg(COMP a)
-{
- COMP res;
-
- res.real = -a.real;
- res.imag = -a.imag;
-
- return res;
-}
-
-static COMP cconj(COMP a)
-{
- COMP res;
-
- res.real = a.real;
- res.imag = -a.imag;
-
- return res;
-}
-
-static COMP cmult(COMP a, COMP b)
-{
- COMP res;
-
- res.real = a.real*b.real - a.imag*b.imag;
- res.imag = a.real*b.imag + a.imag*b.real;
-
- return res;
-}
-
-static COMP fcmult(float a, COMP b)
-{
- COMP res;
-
- res.real = a*b.real;
- res.imag = a*b.imag;
-
- return res;
-}
-
-static COMP cadd(COMP a, COMP b)
-{
- COMP res;
-
- res.real = a.real + b.real;
- res.imag = a.imag + b.imag;
-
- return res;
-}
-
-static float cabsolute(COMP a)
-{
- return sqrtf(powf(a.real, 2.0) + powf(a.imag, 2.0));
-}
-
-/*---------------------------------------------------------------------------*\
+/*--------------------------------------------------------------------------* \
FUNCTION....: fdmdv_create
AUTHOR......: David Rowe
#include "codec2_fm.h"
#include "fm_fir_coeff.h"
+#include "comp_prim.h"
/*---------------------------------------------------------------------------*\
\*---------------------------------------------------------------------------*/
-static COMP cconj(COMP a)
-{
- COMP res;
-
- res.real = a.real;
- res.imag = -a.imag;
-
- return res;
-}
-
-static COMP cmult(COMP a, COMP b)
-{
- COMP res;
-
- res.real = a.real*b.real - a.imag*b.imag;
- res.imag = a.real*b.imag + a.imag*b.real;
-
- return res;
-}
-
-static COMP fcmult(float a, COMP b)
-{
- COMP res;
-
- res.real = a*b.real;
- res.imag = a*b.imag;
-
- return res;
-}
-
-static float cabsolute(COMP a)
-{
- return sqrtf(powf(a.real, 2.0) + powf(a.imag, 2.0));
-}
-
/*---------------------------------------------------------------------------*\
FUNCTION....: fm_create
target_link_libraries(tfdmdv codec2)
add_executable(tcohpsk tcohpsk.c ../src/cohpsk.c ../src/octave.c)
-target_link_libraries(tcohpsk)
+target_link_libraries(tcohpsk codec2)
add_executable(t16_8 t16_8.c ../src/fdmdv.c ../src/kiss_fft.c)
target_link_libraries(t16_8 codec2)
#include "codec2_fdmdv.h"
#include "codec2_cohpsk.h"
#include "cohpsk_defs.h"
+#include "cohpsk_internal.h"
#include "test_bits_coh.h"
#include "octave.h"
int main(int argc, char *argv[])
{
- int tx_bits[COHPSK_BITS_PER_FRAME];
- COMP tx_symbols[NSYMROWPILOT][PILOTS_NC];
+ struct COHPSK *coh;
+ int tx_bits[COHPSK_BITS_PER_FRAME];
+ COMP tx_symb[NSYMROWPILOT][PILOTS_NC];
+ int rx_bits[COHPSK_BITS_PER_FRAME];
- int tx_bits_log[COHPSK_BITS_PER_FRAME*FRAMES];
- COMP tx_symbols_log[NSYMROWPILOT*FRAMES][PILOTS_NC];
+ int tx_bits_log[COHPSK_BITS_PER_FRAME*FRAMES];
+ COMP tx_symb_log[NSYMROWPILOT*FRAMES][PILOTS_NC];
+
+ float rx_amp_log[NSYMROW*FRAMES][PILOTS_NC];
+ float rx_phi_log[NSYMROW*FRAMES][PILOTS_NC];
+ int rx_bits_log[COHPSK_BITS_PER_FRAME*FRAMES];
- FILE *fout;
- int f, r,c,rx_sym_log_r;
+ FILE *fout;
+ int f, r, c, log_r, log_data_r;
+
+ coh = cohpsk_create();
+ assert(coh != NULL);
- rx_sym_log_r=0;
+ log_r = log_data_r= 0;
memcpy(tx_bits, test_bits_coh, sizeof(int)*COHPSK_BITS_PER_FRAME);
for(f=0; f<FRAMES; f++) {
/* --------------------------------------------------------*\
- Modulator
+ Modem
\*---------------------------------------------------------*/
- bits_to_qpsk_symbols(tx_symbols, (int*)tx_bits, COHPSK_BITS_PER_FRAME);
+ bits_to_qpsk_symbols(tx_symb, (int*)tx_bits, COHPSK_BITS_PER_FRAME);
+ qpsk_symbols_to_bits(coh, rx_bits, tx_symb);
/* --------------------------------------------------------*\
Log each vector
\*---------------------------------------------------------*/
memcpy(&tx_bits_log[COHPSK_BITS_PER_FRAME*f], tx_bits, sizeof(int)*COHPSK_BITS_PER_FRAME);
- for(r=0; r<NSYMROWPILOT; r++, rx_sym_log_r++) {
+ for(r=0; r<NSYMROWPILOT; r++, log_r++) {
for(c=0; c<PILOTS_NC; c++)
- tx_symbols_log[rx_sym_log_r][c] = tx_symbols[r][c];
+ tx_symb_log[log_r][c] = tx_symb[r][c];
}
- assert(rx_sym_log_r <= NSYMROWPILOT*FRAMES);
- }
+ for(r=0; r<NSYMROW; r++, log_data_r++) {
+ for(c=0; c<PILOTS_NC; c++) {
+ rx_amp_log[log_data_r][c] = coh->amp_[r][c];
+ rx_phi_log[log_data_r][c] = coh->phi_[r][c];
+ }
+ }
+ memcpy(&rx_bits_log[COHPSK_BITS_PER_FRAME*f], rx_bits, sizeof(int)*COHPSK_BITS_PER_FRAME);
+
+ assert(log_r <= NSYMROWPILOT*FRAMES);
+ assert(log_data_r <= NSYMROW*FRAMES);
+ }
/*---------------------------------------------------------*\
Dump logs to Octave file for evaluation
assert(fout != NULL);
fprintf(fout, "# Created by tcohpsk.c\n");
octave_save_int(fout, "tx_bits_log_c", tx_bits_log, 1, COHPSK_BITS_PER_FRAME*FRAMES);
- octave_save_complex(fout, "tx_symbols_log_c", (COMP*)tx_symbols_log, NSYMROWPILOT*FRAMES, PILOTS_NC, PILOTS_NC);
+ octave_save_complex(fout, "tx_symb_log_c", (COMP*)tx_symb_log, NSYMROWPILOT*FRAMES, PILOTS_NC, PILOTS_NC);
+ octave_save_complex(fout, "rx_amp_log_c", (COMP*)rx_amp_log, NSYMROW*FRAMES, PILOTS_NC, PILOTS_NC);
+ octave_save_complex(fout, "rx_phi_log_c", (COMP*)rx_phi_log, NSYMROW*FRAMES, PILOTS_NC, PILOTS_NC);
fclose(fout);
+ cohpsk_destroy(coh);
+
return 0;
}