Игру жизнь — клеточный автомат уже кажется писали на всех возможных языках программирования.
Меня же интересует технология ПЛИС — и поэтому когда-то я сделал реализацию life для ПЛИС Альтера Cyclone III. Правда поместилось в чип тогда очень мало: всего 32×16 клеток. На таком маленьком поле довольно трудно испытать сложные фигуры.
Сейчас у меня в руках другая плата: тут уже стоит Altera MAX10 с 50-ю тысячами логических элементов. Было интересно, смогу ли я расширить поле хотя бы в 4 раза? В общем задумал сделать хотя бы 64×32.
Результат представлен на этом видео, я называю эту картину: «ружье Госпера убивает самоё себя».
Ниже подробности реализации.
Собственно реализация игры у меня уже была в предыдущем моем проекте для третьего циклона. Весь проект как бы состоит из нескольких частей.
Поле игры жизнь составлено из взаимосвязанных модулей-ячеек написанных на Verilog HDL так, чтобы было возможным за 1 такт вычислить все следующее поколение клеток.
Хочется иметь именно такую реализацию, ведь это ПЛИС, значит там можно и нужно делать. Это модель множественных вычислителей, которые работают одновременно параллельно и передают параметры друг другу. Вот этот параллелизм как раз и поражают воображение: поле игры 64×32=2048 параллельных вычислителей работающих в FPGA синхронно! Модуль и вся логика одной ячейки написана на Verilog HDL:
module xcell( input wire clk, input wire seed_ena, input wire life_step, input wire in_up_left, input wire in_up, input wire in_up_right, input wire in_left, input wire in_right, input wire in_down_left, input wire in_down, input wire in_down_right, output reg cell_life ); wire [3:0]neighbor_number; assign neighbor_number = in_up_left + in_up + in_up_right + in_left + in_right + in_down_left + in_down + in_down_right; always @(posedge clk) if(seed_ena) cell_life <= in_left; //do load initial life into cell else if(life_step) //recalculate new generation of life begin if( neighbor_number == 3 ) cell_life <= 1'b1; //born else if( neighbor_number < 2 || neighbor_number > 3 ) cell_life <= 1'b0; //die end endmodule
Потом экземпляры этого модуля многократно создаются и соединяются между собой проводами в единое плоское поле с помощью конструкции generate-endgenerate языка Verilog HDL.
Второй по важности модуль в проекте — это модуль загрузки исходного состояния игры через последовательный порт. Состояние передается в виде текстового файла примерно вот такого вида:
1——**————————
2——*—*————————
3—-*—-*———————-
4—*——*———————
5—*——*———————
6—-*—-*———————-
7——*—*————————
8——**————————
9———————————
A———————————
B———————————
C———————————
D———————————
E———————————
F———————————
Значащие символы только ‘*’ (живая клетка) и ‘-‘ (нет жизни). Скорость передачи 115200, 8 бит, 1 стоп, без четности. Во время загрузки проекта нужно подержать кнопочку на плате — тогда поле жизни будет засеяно новым состоянием, описанным в текстовом файле.
Ну и последнее — модуль отображения текущего состояния игры. Это текстовый видеоадаптер, в который периодически переписывается состояние ячеек-клеток. Все клетки связаны в циклический сдвиговый регистр, так что можно за WIDTH*HEIGHT тактов прочитать все поле игры и сделать запись в видеоадаптер.
Ну конечно, это все вместе получается довольно мудрено — ведь сама логика игры «жизнь» простая, но обслуживающие модули, модули загрузки и отображения оказываются чуть ли не сложнее самой «жизни».
И вот еще про отображение на экране. Чтобы портировать старый проект на MAX10 и для абсолютно другой платы Марсоход3 придется немного повозиться. Дело в том, что на плате уже нет разъема VGA, с которым было так просто и приятно работать. Теперь на плате стоит HDMI разъем и линии HDMI идут прямо к чипу ПЛИС.
Чтобы управлять линиями HDMI прошлось поизучать довольно много материала. За основу был взят проект на www.fpga4fun.com/HDMI.html Здесь довольно подробно все рассказывается.
HDMI использует последовательную передачу через дифференциальную пару. Всего пар четыре. Три пары передают 8-ми битные цвета R, G, B плюс управляющие сигналы HSYNC и VSYNC. Из-за последовательной передачи для TMDS кодирования требуется рабочая частота в 10 раз выше частоты пикселов на экране. Если частота пикселов 74 МГц при разрешении 1280х720, то для кодирования сигнала уже требуется 740 МГц и это очень много. Ситуацию спасает то факт, что на выходах ПЛИС есть встроенные интерфейс DDIO, то есть сериализатор два-к-одному. Значит максимальная частота в проекте может быть снижена до 370 МГц.
Исходный код модуля HDMI приведен ниже.
module hdmi( input wire pixclk, // 74MHz input wire clk_TMDS2, // 370MHz input wire hsync, input wire vsync, input wire active, input wire [7:0]red, input wire [7:0]green, input wire [7:0]blue, output wire TMDS_bh, output wire TMDS_bl, output wire TMDS_gh, output wire TMDS_gl, output wire TMDS_rh, output wire TMDS_rl ); wire [9:0] TMDS_red, TMDS_green, TMDS_blue; TMDS_encoder encode_R(.clk(pixclk), .VD(red ), .CD(2'b00) , .VDE(active), .TMDS(TMDS_red)); TMDS_encoder encode_G(.clk(pixclk), .VD(green), .CD(2'b00) , .VDE(active), .TMDS(TMDS_green)); TMDS_encoder encode_B(.clk(pixclk), .VD(blue ), .CD({vsync,hsync}), .VDE(active), .TMDS(TMDS_blue)); reg [2:0] TMDS_mod5=0; // modulus 5 counter reg [4:0] TMDS_shift_bh=0, TMDS_shift_bl=0; reg [4:0] TMDS_shift_gh=0, TMDS_shift_gl=0; reg [4:0] TMDS_shift_rh=0, TMDS_shift_rl=0; wire [4:0] TMDS_blue_l = {TMDS_blue[9],TMDS_blue[7],TMDS_blue[5],TMDS_blue[3],TMDS_blue[1]}; wire [4:0] TMDS_blue_h = {TMDS_blue[8],TMDS_blue[6],TMDS_blue[4],TMDS_blue[2],TMDS_blue[0]}; wire [4:0] TMDS_green_l = {TMDS_green[9],TMDS_green[7],TMDS_green[5],TMDS_green[3],TMDS_green[1]}; wire [4:0] TMDS_green_h = {TMDS_green[8],TMDS_green[6],TMDS_green[4],TMDS_green[2],TMDS_green[0]}; wire [4:0] TMDS_red_l = {TMDS_red[9],TMDS_red[7],TMDS_red[5],TMDS_red[3],TMDS_red[1]}; wire [4:0] TMDS_red_h = {TMDS_red[8],TMDS_red[6],TMDS_red[4],TMDS_red[2],TMDS_red[0]}; always @(posedge clk_TMDS2) begin TMDS_shift_bh <= TMDS_mod5[2] ? TMDS_blue_h : TMDS_shift_bh [4:1]; TMDS_shift_bl <= TMDS_mod5[2] ? TMDS_blue_l : TMDS_shift_bl [4:1]; TMDS_shift_gh <= TMDS_mod5[2] ? TMDS_green_h : TMDS_shift_gh [4:1]; TMDS_shift_gl <= TMDS_mod5[2] ? TMDS_green_l : TMDS_shift_gl [4:1]; TMDS_shift_rh <= TMDS_mod5[2] ? TMDS_red_h : TMDS_shift_rh [4:1]; TMDS_shift_rl <= TMDS_mod5[2] ? TMDS_red_l : TMDS_shift_rl [4:1]; TMDS_mod5 <= (TMDS_mod5[2]) ? 3'd0 : TMDS_mod5+3'd1; end assign TMDS_bh = TMDS_shift_bh[0]; assign TMDS_bl = TMDS_shift_bl[0]; assign TMDS_gh = TMDS_shift_gh[0]; assign TMDS_gl = TMDS_shift_gl[0]; assign TMDS_rh = TMDS_shift_rh[0]; assign TMDS_rl = TMDS_shift_rl[0]; endmodule module TMDS_encoder( input clk, input [7:0] VD, // video data (red, green or blue) input [1:0] CD, // control data input VDE, // video data enable, to choose between CD (when VDE=0) and VD (when VDE=1) output reg [9:0] TMDS = 0 ); wire [3:0] Nb1s = VD[0] + VD[1] + VD[2] + VD[3] + VD[4] + VD[5] + VD[6] + VD[7]; wire XNOR = (Nb1s>4'd4) || (Nb1s==4'd4 && VD[0]==1'b0); wire [8:0] q_m = {~XNOR, q_m[6:0] ^ VD[7:1] ^ {7{XNOR}}, VD[0]}; reg [3:0] balance_acc = 0; wire [3:0] balance = q_m[0] + q_m[1] + q_m[2] + q_m[3] + q_m[4] + q_m[5] + q_m[6] + q_m[7] - 4'd4; wire balance_sign_eq = (balance[3] == balance_acc[3]); wire invert_q_m = (balance==0 || balance_acc==0) ? ~q_m[8] : balance_sign_eq; wire [3:0] balance_acc_inc = balance - ({q_m[8] ^ ~balance_sign_eq} & ~(balance==0 || balance_acc==0)); wire [3:0] balance_acc_new = invert_q_m ? balance_acc-balance_acc_inc : balance_acc+balance_acc_inc; wire [9:0] TMDS_data = {invert_q_m, q_m[8], q_m[7:0] ^ {8{invert_q_m}}}; wire [9:0] TMDS_code = CD[1] ? (CD[0] ? 10'b1010101011 : 10'b0101010100) : (CD[0] ? 10'b0010101011 : 10'b1101010100); always @(posedge clk) TMDS <= VDE ? TMDS_data : TMDS_code; always @(posedge clk) balance_acc <= VDE ? balance_acc_new : 4'h0; endmodule module ddio( input wire d0, input wire d1, input wire clk, output wire out ); reg r_d0; reg r_d1; always @(posedge clk) begin r_d0 <= d0; r_d1 <= d1; end assign out = clk ? r_d0 : r_d1; endmodule
Весь проект для платы Марсоход3 можно взять на github: github.com/marsohod4you/FPGA_game_life
Отчет компилятора Altera Quartus Prime:
Flow Status Successful — Thu Apr 28 16:08:48 2016
Quartus Prime Version 15.1.0 Build 185 10/21/2015 SJ Lite Edition
Revision Name max10_50
Top-level Entity Name top
Family MAX 10
Device 10M50SAE144C8GES
Timing Models Preliminary
Total logic elements 29,432 / 49,760 ( 59 % )
Total combinational functions 28,948 / 49,760 ( 58 % )
Dedicated logic registers 2,238 / 49,760 ( 4 % )
Total registers 2254
Total pins 23 / 101 ( 23 % )
Total virtual pins 0
Total memory bits 147,456 / 1,677,312 ( 9 % )
Embedded Multiplier 9-bit elements 0 / 288 ( 0 % )
Total PLLs 1 / 1 ( 100 % )
UFM blocks 0 / 1 ( 0 % )
ADC blocks 0 / 1 ( 0 % )
Вероятно игра «жизнь» уже многим надоела. Однако, на мой взгляд тут есть над чем поразмыслить. Несмотря на свою простоту, в ней заложены интересные принципы взаимосвязанных вычислителей. Вероятно, похожие идеи могут быть использованы в специальных классах задач. Например, размещение компонентов на печатной плате — это сложная комбинаторная задача, которая должна учитывать множество факторов и в том числе длины связей между компонентами. Можно представить себе, что компоненты на печатной плате — это клетки, борющиеся за более удачное расположение на поле жизни под влиянием сил связей между компонентами. Думаю, что со временем такие задачи будут рассчитываться с помощью FPGA.
ссылка на оригинал статьи https://habrahabr.ru/post/282722/
Добавить комментарий