После первых опытов с ПЛИС, остался осадок, что чего то не хватает. Поразмыслив стало понятно, не хватает периферии, например какого нибудь интерфейса, для передачи данных.
Собственно зачем это нужно. Как вы могли уже понять скорость у ПЛИС очень высокая, но ресурсов довольно не много, по крайней мере у EPM240. Что наводит на мысль использования ПЛИС только как быстрого исполнителя, при этом в качестве «мозгов» можно использовать микроконтроллер. Одно маленькое «но», как их подружить между собой. На мой взгляд, самый популярный интерфейс это Uart.
Verilog все еще трудно воспринимается, видимо дело привычки, поэтому здесь не будет красивых и оптимизированных кусков кода. Просто расскажу как я действовал, поэтому стоит немного рассказать про то как передаются данные. Изначально передатчик и приемник «договариваются» о скорости, на которой будет производиться обмен. Настройка скорости и пр. делается индивидуально, для каждого устройства. Если микроконтроллер, то это записывается в программе. Если Com порт на пк, то выставляется в настройках программы использующей его.
Когда данные не передаются, передатчик притягивает линию к логической единице. Допустим мы решили передать символ ‘0’ в кодировке ASCII он идет под номером 0x30 или 0b110000. Первым пойдет стартбит, независимо от остальных данных, он всегда будет нулем. Этот логический ноль на ножке передатчика будет в течение времени t=1/s где s-скорость передачи бит в секунду. Далее последует нулевой бит наших данных, в данном случае 0(0b110000), который будет на ножке еще 1/s секунд.
Соответственно дальше пойдут следующие данные. До тех пор пока все восемь бит не уйдут. По окончании передачи на ножке установится стопбит, который всегда равен единице. После этого мы готовы принимать следующий символ
Все выше сказанное справедливо, если в настройках указано тип передачи 8-N-1, т.е. 8 бит данных, нет контроля четности, один стоп бит. Если настройки другие, то и суть программы будет меняться. Немного стоит сказать про скорость передачи. Существует общепринятый ряд скоростей, по умолчанию скорость равна 9600 бит/с. Изначально идея была выжать побольше, поэтому было решено использовать 57600 бит/с. Вообще с той точки зрения, что кварц у нас быстрый, то чем выше будет выбрана скорость, тем меньше будет использовано ресурсов.
Как же это реализовать. Так как нам требуются четкие временные промежутки, то другого варианта как использовать кварцевый генератор у нас нет. На борту моей платы генератор 100МГц. В качестве привязки будем использовать положительный фронт тактовой частоты, т.е. код находящийся внутри данного цикла будет выполняться в моменты указанные стрелочкой.
always @ (posedge clk) begin .... .... end |
Кварцевый генератор на 100МГц, т.е. за 1 секунду он сделает 100 000 000 колебаний, период будет равен 0,01мкс. Один бит будет передаваться 1/57600 = 17,36мкс. Таким образом, чтобы ушел старт бит, необходимо выставить логическую единицу и внутри цикла отсчитать 17,36/0,01 = 1736 раз. Затем выставить нулевой бит данных, сделать еще 1736 тиков и т.д. пока все 10 битов не будут отправлены.
module test_uart(clk,Tx); input clk; //ножка к которой подключен кварц output reg Tx=1'b1; //ножка которая выдает сигнал uart reg [11:0]count = 1'b0; //счетчик для пауз в 1736 reg [9:0]data = 10'b1001100000; //данные, аски код '0' reg [4:0]i= 1'b0; //счетчик битов always @ (posedge clk) //с каждым тактом begin count <= count + 1'b1; //увеличивать счетчик if(count==1736) //когда досчитали до 1736 begin Tx <= data[i]; //отправили 1 бит данных count <= 0; //обнулили счетчик i <= i + 1; //перешли к следующему биту данных if(i>9) // если все 10 бит отправлены begin i <= 0; //обнулить счетчик битов Tx <=1'b1; //подтянуть ножку к лог 1 end end end endmodule |
Результат предсказуем, программа постоянно отправляет нули в терминал, тем не менее это уже что то, ведь результат достигнут, комп общается с ПЛИС, пусть и так коряво.
Ради интереса решил привязать отправку байта к нажатию кнопки с неким подобием «антидребезга». Если кнопка нажата, то увеличивается счетчик, если счетчик перевалил за 16000000 (подобрано опытным путем), то считаем что кнопка нажата. Если кнопка отжата, то отбавляем от счетчика значения. В итоге получилась смачная каша))) которая тем не менее работает.
module test_uart(clk,Tx,button); input clk; //вход для тактовой output reg Tx=1'b1; //выход юарта input button; //вход кнопки reg [1:0]TxEnable = 1'b0; //разрешаем отправку reg [1:0]btnFlag = 1'b0; //флаг состояния кнопки reg [11:0]count = 1'b0; reg [9:0]data = 10'b1001100000; reg [4:0]i= 1'b0; reg [23:0]delay = 0; //счетчик "нажатости" кнопки always @ (button) //смотрим в каком положении кнопка begin //и выставляем флаг if(button == 1) btnFlag <= 1'b0; //если не нажата else begin btnFlag <= 1'b1; //если нажата end end always @ (posedge clk) begin if(btnFlag == 1'b1) //если нажата begin delay <= delay + 1'b1; //увеличиваем счетчик end if((btnFlag == 1'b0) &&(delay > 0)) //если не нажата begin delay <= delay - 1'b1; //уменьшаем счетчик end if(delay== 16000000) //если счетчик досчитал до устойчивого нажатия begin TxEnable <= 1'b1; //разрешаем передачу end if(TxEnable == 1'b1) //если разрешено, то отправляем begin count <= count + 1'b1; if(count==1736) begin Tx <= data[i]; count <= 0; i <= i + 1; if(i>9) begin i <= 0; Tx <=1'b1; TxEnable <= 1'b0; //запрещаем повторную отправку end end end end endmodule |
Результат — нажали кнопку, один символ ушел
Про прием расскажу отдельно, чтобы не было мешанины. В принципе тут тоже самое: смотрим состояние входа ножки Rx. Как только ноль — значит пошел стартбит, отсчитали полбита 1736/2=868, разрешили считывать данные. Отсчитали еще 1736 находимся где то в середине нулевого бита, прочитали значение. Отсчитали еще 1736 находимся посередине 1 бита прочитали и т.д. По окончанию сравниваем полученный байт с нужным.
Тестовая программа, принимающая байт, если байт равен ‘0’ в кодировке ASCII, то зажигаем светодиод. Если нет, то тушим.
module test_uart(clk,Rx,led); input clk; input Rx; //ножка приема output reg led = 0; //светодиод reg [1:0]RxEnable = 1'b0; //прием разрешен reg [11:0]count = 1'b0; reg [9:0]data = 10'b1001100000; //сюда складываем принятые биты reg [4:0]i= 1'b0; reg [7:0]ASCII = 8'b00110000; //символ нуля в Ascii с ним сравниваем принятые биты always @ (posedge clk) begin if((Rx==0)&&(i==0)) //если на входе ноль, значит пошел стартбит - разрешаем прием begin RxEnable <= 1'b1; end if(RxEnable == 1'b1) //если прием разрешен begin count <= count + 1'b1; //считаем if((count==868)&&(i==0)) //когда достигли середины стартбита begin i <= 1; //переключаемся на нулевой бит count <= 0; end if((i > 0)&&(count == 1736)) //если анализируем нулевой бит и счетчик отсчитал begin data[i] <= Rx; //записываем принятый бит count <= 0; //обнуляем счетчик i <= i + 1; //переходим к следующему биту if(i>9) //когда все биты приняты begin i <= 0; RxEnable <= 1'b0; //запрещаем прием end end end if(RxEnable == 1'b0) //когда прием запрещен begin if(data[8:1]==ASCII[7:0]) //смотрим нужно ли зажигать светодиод led = 1'b1; end else begin led = 1'b0; end end endmodule |
Пусть немного кривовато, но работает, суть в том что теперь можно соединять ПЛИС, с ПК, с мк. В общем то сложности есть, но пока они решаемы. По сути я показал лишь свою реализацию, на самом деле все блоки подобные юарту уже давно реализованы в модулях и доступны в сети. Достаточно взять готовый и использовать в своем проекте. Думаю постепенно можно и с плисинами освоиться, но естественно не сразу 🙂
А как насчёт реализации UART в виде независимого блока, как в Avr, где принятый байт помещается в буфер приёма и ставится флаг и генерируется прерывание программы?
да можно реализовать полностью state machine, задача достойная, как следующий шаг. если это вопрос будет ли такая статья, то если и будет то не в ближайшее время.
Вполне хороший пример.
Если хотите «крутое-навороченное», почитайте книгу Понг Чу.
Там их две — одна для верилога, вторая — vhdl